0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-23 23:18:48 -05:00

Add new heading component to the DS

This commit is contained in:
Eva Marco 2024-07-05 09:01:35 +02:00
parent 808ed6a98b
commit 2abbb0d359
14 changed files with 634 additions and 36 deletions

View file

@ -14,7 +14,9 @@
url($filepath + ".ttf") format("truetype");
font-weight: unquote($weight);
font-style: unquote($style);
unicode-range: $unicode-range;
@if $unicode-range {
unicode-range: $unicode-range;
}
}
}
@ -35,18 +37,18 @@ $_latin-unicode-list: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+
@include font-face-variable("vazirmatn", "Vazirmatn-VariableFont", $_arabic-unicode-list);
// Source Sans Pro
@include font-face("sourcesanspro", "sourcesanspro-extralight", $_latin-unicode-list, "200");
@include font-face("sourcesanspro", "sourcesanspro-extralightitalic", $_latin-unicode-list, "200", italic);
@include font-face("sourcesanspro", "sourcesanspro-light", $_latin-unicode-list, "300");
@include font-face("sourcesanspro", "sourcesanspro-lightitalic", $_latin-unicode-list, "300", italic);
@include font-face("sourcesanspro", "sourcesanspro-regular", $_latin-unicode-list, normal);
@include font-face("sourcesanspro", "sourcesanspro-italic", $_latin-unicode-list, normal, italic);
@include font-face("sourcesanspro", "sourcesanspro-semibold", $_latin-unicode-list, "600");
@include font-face("sourcesanspro", "sourcesanspro-semibolditalic", $_latin-unicode-list, "600", italic);
@include font-face("sourcesanspro", "sourcesanspro-bold", $_latin-unicode-list, bold);
@include font-face("sourcesanspro", "sourcesanspro-bolditalic", $_latin-unicode-list, bold, italic);
@include font-face("sourcesanspro", "sourcesanspro-black", $_latin-unicode-list, "900");
@include font-face("sourcesanspro", "sourcesanspro-blackitalic", $_latin-unicode-list, "900", italic);
@include font-face("sourcesanspro", "sourcesanspro-extralight", "200");
@include font-face("sourcesanspro", "sourcesanspro-extralightitalic", "200", italic);
@include font-face("sourcesanspro", "sourcesanspro-light", "300");
@include font-face("sourcesanspro", "sourcesanspro-lightitalic", "300", italic);
@include font-face("sourcesanspro", "sourcesanspro-regular", normal);
@include font-face("sourcesanspro", "sourcesanspro-italic", normal, italic);
@include font-face("sourcesanspro", "sourcesanspro-semibold", "600");
@include font-face("sourcesanspro", "sourcesanspro-semibolditalic", "600", italic);
@include font-face("sourcesanspro", "sourcesanspro-bold", bold);
@include font-face("sourcesanspro", "sourcesanspro-bolditalic", bold, italic);
@include font-face("sourcesanspro", "sourcesanspro-black", "900");
@include font-face("sourcesanspro", "sourcesanspro-blackitalic", "900", italic);
// Roboto mono
@include font-face("robotomono", "RobotoMono-Regular", $_latin-unicode-list, normal);

View file

@ -6,17 +6,25 @@
(ns app.main.ui.ds
(:require
[app.main.ui.ds.foundations.heading :refer [heading*]]
[app.main.ui.ds.foundations.icon :refer [icon* icon-list]]
[app.main.ui.ds.foundations.raw-svg :refer [raw-svg* raw-svg-list]]
[app.main.ui.ds.foundations.text :refer [text*]]
[app.main.ui.ds.foundations.typography :refer [typography-list]]
[app.main.ui.ds.storybook :as sb]))
(def default
"A export used for storybook"
#js {:Icon icon*
#js {:Heading heading*
:Icon icon*
:RawSvg raw-svg*
:Text text*
;; meta / misc
:meta #js {:icons icon-list :svgs raw-svg-list}
:meta #js {:icons icon-list
:svgs raw-svg-list
:typography (clj->js typography-list)}
:storybook #js {:StoryGrid sb/story-grid*
:StoryGridCell sb/story-grid-cell*
:StoryGridRow sb/story-grid-row*
:StoryHeader sb/story-header*
:StoryWrapper sb/story-wrapper*}})

View file

@ -0,0 +1,47 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.foundations.heading
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.typography :refer [typography-list]]
[rumext.v2 :as mf]))
(defn- valid-level? [value]
(let [number-set #{"1" "2" "3" "4" "5" "6"}]
(contains? number-set (dm/str value))))
(defn- valid-typography? [value]
(contains? typography-list value))
(mf/defc heading*
{::mf/props :obj}
[{:keys [level typography class children] :rest props}]
(assert (or (valid-level? level)
(nil? level))
(dm/str "Invalid level: " level ". Valid numbers are 1 to 6."))
(assert (valid-typography? (dm/str typography))
(dm/str typography " is an unknown typography"))
(let [level (or level "1")
tag (dm/str "h" level)
class (dm/str (or class "") " " (stl/css-case :display-typography (= typography "display")
:title-large-typography (= typography "title-large")
:title-medium-typography (= typography "title-medium")
:title-small-typography (= typography "title-small")
:headline-large-typography (= typography "headline-large")
:headline-medium-typography (= typography "headline-medium")
:headline-small-typography (= typography "headline-small")
:body-large-typography (= typography "body-large")
:body-medium-typography (= typography "body-medium")
:body-small-typography (= typography "body-small")
:code-font-typography (= typography "code-font")))
props (mf/spread-props props {:class class})]
[:> tag props
children]))

View file

@ -0,0 +1,49 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as HeadingStories from "./heading.stories";
<Meta of={HeadingStories} />
# Headings
This component will add a heading tag element to our code.
## Technical notes
This components accepts to props:
- `level` (default value: `1`) : A number from `1` to `6`, to set the heading level (i.e. `<h1>`, `<h2>`, etc.).
- `typography` (mandatory): Any of the [supported typography IDs](?path=/docs/foundations-typography--docs).
You can check passed props to renderized components on hover `level / typography`;
### Using typography IDs
There are typography ID definitions you can use in your code rather than typing the
typography ID by hand.
**Using these IDs is recommended**.
Assuming the namespace of the typography is required as `t`:
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.heading :refer [heading*]]
[app.main.ui.ds.foundations.typography :as t]))
```
You can now use the typography IDs defined in the namespace:
```clj
[:> heading* {:typography t/title-large} "Welcome to Penpot"]
```
## Accesibility
There should only be one level 1 heading `<h1>` per page.
Headings are used to navigate the page and must follow the `<h1>` → `<h2>` → `<h3>` → `<h4>` → `<h5>` → `<h6>` hierarchy.
For example, do not skip levels in the `<h1>` → `<h3>` hierarchy if there is no `<h2>` in between.
We should not choose the heading level by its visual aspect.

View file

@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "../typography.scss" as t;
.display-typography {
@include t.use-typography("display");
}
.title-large-typography {
@include t.use-typography("title-large");
}
.title-medium-typography {
@include t.use-typography("title-medium");
}
.title-small-typography {
@include t.use-typography("title-small");
}
.headline-large-typography {
@include t.use-typography("headline-large");
}
.headline-medium-typography {
@include t.use-typography("headline-medium");
}
.headline-small-typography {
@include t.use-typography("headline-small");
}
.body-large-typography {
@include t.use-typography("body-large");
}
.body-medium-typography {
@include t.use-typography("body-medium");
}
.body-small-typography {
@include t.use-typography("body-small");
}
.code-font-typography {
@include t.use-typography("code-font");
}

View file

@ -0,0 +1,54 @@
import * as React from "react";
import Components from "@target/components";
const { Heading } = Components;
const { StoryWrapper, StoryGridRow } = Components.storybook;
export default {
title: "Foundations/Heading",
component: Components.Heading,
};
export const Levels = {
render: () => (
<StoryWrapper theme="default">
<StoryGridRow title={"1 / display"}>
<Heading level="1" typography="display">
h1 / display
</Heading>
</StoryGridRow>
<StoryGridRow title={"2 / display"}>
<Heading level="2" typography="display">
h2 / display
</Heading>
</StoryGridRow>
<StoryGridRow title={"3 / display"}>
<Heading level="3" typography="display">
h3 / display
</Heading>
</StoryGridRow>
</StoryWrapper>
),
};
export const HeadingTypography = {
render: () => (
<StoryWrapper theme="default">
<StoryGridRow title={"1 / title-large"}>
<Heading level="1" typography="title-large">
h1 / title-large
</Heading>
</StoryGridRow>
<StoryGridRow title={"1 / title-medium"}>
<Heading level="1" typography="title-medium">
h1 / title-medium
</Heading>
</StoryGridRow>
<StoryGridRow title={"1 / code-font"}>
<Heading level="1" typography="code-font">
h1 / code-font
</Heading>
</StoryGridRow>
</StoryWrapper>
),
};

View file

@ -1,3 +1,9 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
.icon {
fill: none;
stroke: currentColor;

View file

@ -0,0 +1,37 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.foundations.text
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.typography :refer [typography-list]]
[rumext.v2 :as mf]))
(defn- valid-typography? [value]
(contains? typography-list value))
(mf/defc text*
{::mf/props :obj}
[{:keys [tag typography children] :rest props}]
(assert (valid-typography? (dm/str typography))
(dm/str typography " is an unknown typography"))
(let [props (mf/spread-props props {:class (stl/css-case :display-typography (= typography "display")
:title-large-typography (= typography "title-large")
:title-medium-typography (= typography "title-medium")
:title-small-typography (= typography "title-small")
:headline-large-typography (= typography "headline-large")
:headline-medium-typography (= typography "headline-medium")
:headline-small-typography (= typography "headline-small")
:body-large-typography (= typography "body-large")
:body-medium-typography (= typography "body-medium")
:body-small-typography (= typography "body-small")
:code-font-typography (= typography "code-font"))})]
[:> tag props
children]))

View file

@ -0,0 +1,32 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.foundations.typography)
(def ^:typography-id display "display")
(def ^:typography-id title-large "title-large")
(def ^:typography-id title-medium "title-medium")
(def ^:typography-id title-small "title-small")
(def ^:typography-id headline-large "headline-large")
(def ^:typography-id headline-medium "headline-medium")
(def ^:typography-id headline-small "headline-small")
(def ^:typography-id body-large "body-large")
(def ^:typography-id body-medium "body-medium")
(def ^:typography-id body-small "body-small")
(def ^:typography-id code-font "code-font")
(def typography-list #{display
title-large
title-medium
title-small
headline-large
headline-medium
headline-small
body-large
body-medium
body-small
code-font})

View file

@ -0,0 +1,153 @@
import { Canvas, Meta, Story } from "@storybook/blocks";
import * as TypographyStories from "./typography.stories";
import Components from "@target/components";
<Meta of={TypographyStories} />
# Typography
We currently work with 3 scales depending on whether the typography
is applied in the workspace, in the dashboard and access areas
and in the communication modalities.
This situation is something to be corrected in future improvements.
## Usage
### Standard text colours
Typographic colours are used in text elements such as headers
and body and in the various components that make up the tool...
The colours used in typography are the Foreground colours such
`--color-foreground-primary` or `--color-foreground-secondary`
but different colours can be applied in specific component
applications with their own styles, such as buttons.
As far as possible and as long as accessibility is not affected,
we will use colour in typography as a way of hierarchising
the content displayed using tone, built in luminance changes and
size as differentiating elements between main and secondary text.
### Status text colous
Another possible application of colour is semantic, when we use text to convey some system state.
Use the tokens:
`--color-accent-success` - Use as text colour to indicate success.
`--color-accent-warning` - Use as text colour to indicate a warning or caution.
`--color-accent-error` - Use as text colour to indicate an error.
## Accesibility
Typefaces should be sized for legibility and accessibility.
The minimum size for texts in Penpot will be 14px except
for exceptions based on the size of the components.
## Title
### Display `display`
Hero style text for transitional pages (Login). If too large use large title in narrow windows.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="display" >Display - 400 - 36px/1.4 "Work Sans" </Components.Heading>
</Canvas>
### Title large `title-large`
Page headers for main pages (dashboard, Profiles...). If too big use title
(medium) in narrow windows.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="title-large" >Title large - 400 - 24px/1.4 "Work Sans" </Components.Heading>
</Canvas>
### Title medium `title-medium`
Default page title. Equivalent line height of 32px matches the height
of buttons and other medium controls. Ideal for page header layout.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="title-medium" >Title medium - 400 - 20px/1.4 "Work Sans"</Components.Heading>
</Canvas>
### Title small `title-small`
Uses the same size as body (large).
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="title-small" >Title small - 400 - 14px/1.2 "Work Sans"</Components.Heading>
</Canvas>
## Headline
Page sections/subtitles, or names of less important objects in
page titles (automated action titles, for example). Same line height as title (medium).
### Headline large `headline-large`
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="headline-large" >Headline large - 400 - 18px/1.4 "Work Sans"</Components.Heading>
</Canvas>
### Headline medium `headline-medium`
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="headline-medium" >Headline medium - 400 - 16px/1.4 "Work Sans"</Components.Heading>
</Canvas>
### Headline small `headline-small`
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="headline-small" >Headline small - 500 - 12px/1.2 "Work Sans"</Components.Heading>
</Canvas>
## Body
### Body large `body-large`
Generic content.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="body-large" >Body large - 400 - 16px/1.4 "Work Sans"</Components.Heading>
</Canvas>
### Body medium `body-medium`
Default UI font. Most commonly used for body text.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="body-medium" >Body medium - 400 - 14px/1.3 "Work Sans"</Components.Heading>
</Canvas>
### Body small `body-small`
Small compact font with a line height of less than 16px.
Use for single line scenarios, as the small size does not meet accessibility requirements.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="body-small" >Body small - 400 - 12px/1.3 "Work Sans"</Components.Heading>
</Canvas>
## Code font `code-font`
Default style for rendering code blocks.
<Canvas style={{background: "var(--color-background-primary)", color: "var(--color-foreground-primary)"}}>
<Components.Heading level="1" typography="code-font" >Code font - 400 - 12px/1.2 "Roboto Mono"</Components.Heading>
</Canvas>
## Fonts
We are using 3 fonts
- The default font family is `Work Sans`. It was chosen for its efficiency and legibility.
- In case of having the interface in Farsi or Arabic this font will be replaced by `Vazirmatn`.
- For code blocks we are useng `Roboto Mono`.

View file

@ -0,0 +1,135 @@
import * as React from "react";
import Components from "@target/components";
const { Heading } = Components;
const { StoryWrapper, StoryHeader, StoryGridRow } = Components.storybook;
const typographyList = {
display: {
name: "Display",
id: "display",
size: "36px",
weight: "400",
line: "1.4",
uppercase: false,
font: "Work Sans",
},
titleLarge: {
name: "Title large",
id: "title-large",
size: "24px",
weight: "400",
line: "1.4",
uppercase: false,
font: "Work Sans",
},
titleMedium: {
name: "Title medium",
id: "title-medium",
size: "20px",
weight: "400",
line: "1.4",
uppercase: false,
font: "Work Sans",
},
titleSmall: {
name: "Title small",
id: "title-small",
size: "14px",
weight: "400",
line: "1.2",
uppercase: false,
font: "Work Sans",
},
headlineLarge: {
name: "Headline large",
id: "headline-large",
size: "18px",
weight: "400",
line: "1.4",
uppercase: true,
font: "Work Sans",
},
headlineMedium: {
name: "Headline medium",
id: "headline-medium",
size: "16px",
weight: "400",
line: "1.4",
uppercase: true,
font: "Work Sans",
},
headlineSmall: {
name: "Headline small",
id: "headline-small",
size: "12px",
weight: "500",
line: "1.2",
uppercase: true,
font: "Work Sans",
},
bodyLarge: {
name: "Body large",
id: "body-large",
size: "16px",
weight: "400",
line: "1.4",
uppercase: false,
font: "Work Sans",
},
bodyMedium: {
name: "Body medium",
id: "body-medium",
size: "14px",
weight: "400",
line: "1.3",
uppercase: false,
font: "Work Sans",
},
bodySmall: {
name: "Body small",
id: "body-small",
size: "12px",
weight: "400",
line: "1.3",
uppercase: false,
font: "Work Sans",
},
codeFont: {
name: "Code font",
id: "code-font",
size: "12px",
weight: "400",
line: "1.2",
uppercase: false,
font: "Roboto Mono",
},
};
export default {
title: "Foundations/Typography",
component: Components.StoryHeader,
};
export const AllTypography = {
render: () => (
<StoryWrapper theme="default">
<StoryHeader>
<h1>All Typography</h1>
<p>Hover on a heading to see its ID</p>
</StoryHeader>
{Object.values(typographyList).map(
({ id, name, size, weight, line, font }) => (
<StoryGridRow title={id} key={id}>
<Heading level="1" typography={id}>
{name} - {weight} - {size}/{line} {font}
</Heading>
</StoryGridRow>
),
)}
</StoryWrapper>
),
parameters: {
backgrounds: { disable: true },
},
};

View file

@ -45,3 +45,10 @@
(let [class (stl/css :story-header)
props (mf/spread-props other {:class class})]
[:> "header" props children]))
(mf/defc story-grid-row*
{::mf/props :obj}
[{:keys [children] :rest other}]
(let [class (stl/css :story-grid-row)
props (mf/spread-props other {:class class})]
[:> "article" props children]))

View file

@ -19,3 +19,11 @@
.story-header {
color: var(--color-foreground-primary);
}
.story-grid-row {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 60px;
gap: 1rem;
color: var(--color-foreground-primary);
}

View file

@ -13,24 +13,24 @@ $_font-lineheight-dense: 1.2;
$_font-lineheight-compact: 1.3;
$_font-lineheight-normal: 1.4;
@function px2Rem($value) {
@function px2rem($value) {
$remValue: math.div($value, 16) * 1rem;
@return $remValue;
}
$_fs-12: px2Rem(12);
$_fs-14: px2Rem(14);
$_fs-16: px2Rem(16);
$_fs-18: px2Rem(18);
$_fs-20: px2Rem(20);
$_fs-24: px2Rem(24);
$_fs-36: px2Rem(36);
$_fs-12: px2rem(12);
$_fs-14: px2rem(14);
$_fs-16: px2rem(16);
$_fs-18: px2rem(18);
$_fs-20: px2rem(20);
$_fs-24: px2rem(24);
$_fs-36: px2rem(36);
@mixin _font-style-display {
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
line-height: $_font-lineheight-dense;
line-height: $_font-lineheight-normal;
font-size: $_fs-36;
}
@ -38,7 +38,7 @@ $_fs-36: px2Rem(36);
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
line-height: $_font-lineheight-dense;
line-height: $_font-lineheight-normal;
font-size: $_fs-24;
}
@ -46,11 +46,19 @@ $_fs-36: px2Rem(36);
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
line-height: $_font-lineheight-dense;
line-height: $_font-lineheight-normal;
font-size: $_fs-20;
}
@mixin _font-style-heading-large {
@mixin _font-style-title-small {
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
line-height: $_font-lineheight-dense;
font-size: $_fs-14;
}
@mixin _font-style-headline-large {
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
@ -59,16 +67,16 @@ $_fs-36: px2Rem(36);
text-transform: uppercase;
}
@mixin _font-style-heading-medium {
@mixin _font-style-headline-medium {
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-regular;
line-height: $_font-lineheight-dense;
line-height: $_font-lineheight-normal;
font-size: $_fs-16;
text-transform: uppercase;
}
@mixin _font-style-heading-small {
@mixin _font-style-headline-small {
font-family: "worksans", "vazirmatn", sans-serif;
font-optical-sizing: auto;
font-weight: $_font-weight-medium;
@ -116,12 +124,14 @@ $_fs-36: px2Rem(36);
@include _font-style-title-large;
} @else if $typography-name == "title-medium" {
@include _font-style-title-medium;
} @else if $typography-name == "heading-large" {
@include _font-style-heading-large;
} @else if $typography-name == "heading-medium" {
@include _font-style-heading-medium;
} @else if $typography-name == "heading-small" {
@include _font-style-heading-small;
} @else if $typography-name == "title-small" {
@include _font-style-title-small;
} @else if $typography-name == "headline-large" {
@include _font-style-headline-large;
} @else if $typography-name == "headline-medium" {
@include _font-style-headline-medium;
} @else if $typography-name == "headline-small" {
@include _font-style-headline-small;
} @else if $typography-name == "body-large" {
@include _font-style-body-large;
} @else if $typography-name == "body-medium" {