@duro-app/ui 5.20.0 → 5.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Heading/Heading.d.ts +10 -2
- package/dist/components/Heading/Heading.d.ts.map +1 -1
- package/dist/components/Heading/Heading.meta.d.ts.map +1 -1
- package/dist/components/Heading/Heading.stories.d.ts +5 -0
- package/dist/components/Heading/Heading.stories.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1222 -1182
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Heading/Heading.meta.ts +6 -3
- package/src/components/Heading/Heading.stories.tsx +45 -0
- package/src/components/Heading/Heading.tsx +51 -11
- package/src/components/Text/Text.meta.ts +2 -2
- package/src/docs/Typography.mdx +19 -15
- package/src/index.native.ts +1 -1
- package/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"dependencies": {
|
|
93
93
|
"@tanstack/react-virtual": "^3.14.13",
|
|
94
94
|
"qrcode": "^1.5.4",
|
|
95
|
-
"@duro-app/tokens": "^5.
|
|
95
|
+
"@duro-app/tokens": "^5.21.0"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -2,11 +2,12 @@ import type {ComponentMeta} from '../component-meta'
|
|
|
2
2
|
|
|
3
3
|
export const meta: ComponentMeta = {
|
|
4
4
|
description:
|
|
5
|
-
'Semantic heading element (h1-h6) with typography presets. Always use the correct level for document hierarchy.
|
|
5
|
+
'Semantic heading element (h1-h6) with typography presets. Always use the correct level for document hierarchy. The look is `variant` (heading, display, overline) and its size is `size` (sm, md, lg, xl), both independent of the semantic level. The old combined variants (headingSm, displayLg, …) still work and are deprecated.',
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Page titles (level 1)',
|
|
8
8
|
'Section headers (level 2-3)',
|
|
9
9
|
'Card or panel titles (level 3-4)',
|
|
10
|
+
'Group titles over a list of rows or a form section (level 3-4, variant="overline", any size)',
|
|
10
11
|
],
|
|
11
12
|
whenNotToUse: [
|
|
12
13
|
'Body text — use Text',
|
|
@@ -22,7 +23,9 @@ export const meta: ComponentMeta = {
|
|
|
22
23
|
],
|
|
23
24
|
example: `<Stack gap="md">
|
|
24
25
|
<Heading level={1}>Page Title</Heading>
|
|
25
|
-
<Heading level={2}
|
|
26
|
-
<Heading level={
|
|
26
|
+
<Heading level={2} size="md">Section</Heading>
|
|
27
|
+
<Heading level={1} variant="display" size="lg">Hero</Heading>
|
|
28
|
+
<Heading level={3} variant="overline" color="muted">Group</Heading>
|
|
29
|
+
<Heading level={2} variant="overline" size="lg">Large group</Heading>
|
|
27
30
|
</Stack>`,
|
|
28
31
|
}
|
|
@@ -49,3 +49,48 @@ export const WithId: Story = {
|
|
|
49
49
|
await expect(canvas.getByRole('link', {name: 'Install'})).toHaveAttribute('href', '#install')
|
|
50
50
|
},
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/** `variant` is the look and `size` its size, independent of the level: a group title over a
|
|
54
|
+
* list of rows is an `overline` heading, at any size. */
|
|
55
|
+
export const Overline: Story = {
|
|
56
|
+
render: () => (
|
|
57
|
+
<Stack gap="xs">
|
|
58
|
+
<Heading level={3} variant="overline" color="muted">
|
|
59
|
+
Today
|
|
60
|
+
</Heading>
|
|
61
|
+
<Heading level={3} variant="overline" size="md" color="muted">
|
|
62
|
+
Earlier
|
|
63
|
+
</Heading>
|
|
64
|
+
<Heading level={2} variant="overline" size="lg">
|
|
65
|
+
Archive
|
|
66
|
+
</Heading>
|
|
67
|
+
</Stack>
|
|
68
|
+
),
|
|
69
|
+
play: async ({canvas}) => {
|
|
70
|
+
await expect(canvas.getByRole('heading', {level: 3, name: 'Today'})).toBeInTheDocument()
|
|
71
|
+
await expect(canvas.getAllByRole('heading', {level: 3})).toHaveLength(2)
|
|
72
|
+
const sm = getComputedStyle(canvas.getByRole('heading', {name: 'Today'})).fontSize
|
|
73
|
+
const lg = getComputedStyle(canvas.getByRole('heading', {name: 'Archive'})).fontSize
|
|
74
|
+
await expect(parseFloat(lg)).toBeGreaterThan(parseFloat(sm))
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The old combined variants still render the same preset as the two props. */
|
|
79
|
+
export const LegacyVariant: Story = {
|
|
80
|
+
render: () => (
|
|
81
|
+
<Stack gap="xs">
|
|
82
|
+
<Heading level={2} variant="headingSm">
|
|
83
|
+
Legacy
|
|
84
|
+
</Heading>
|
|
85
|
+
<Heading level={2} variant="heading" size="sm">
|
|
86
|
+
Current
|
|
87
|
+
</Heading>
|
|
88
|
+
</Stack>
|
|
89
|
+
),
|
|
90
|
+
play: async ({canvas}) => {
|
|
91
|
+
const legacy = getComputedStyle(canvas.getByRole('heading', {name: 'Legacy'}))
|
|
92
|
+
const current = getComputedStyle(canvas.getByRole('heading', {name: 'Current'}))
|
|
93
|
+
await expect(legacy.fontSize).toBe(current.fontSize)
|
|
94
|
+
await expect(legacy.fontWeight).toBe(current.fontWeight)
|
|
95
|
+
},
|
|
96
|
+
}
|
|
@@ -3,7 +3,12 @@ import {html} from 'react-strict-dom'
|
|
|
3
3
|
import {typePresets} from '@duro-app/tokens/tokens/type-presets.css'
|
|
4
4
|
import {styles} from './styles.css'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/** The heading's look; its size is the `size` prop. */
|
|
7
|
+
export type HeadingFamily = 'heading' | 'display' | 'overline'
|
|
8
|
+
export type HeadingSize = 'sm' | 'md' | 'lg' | 'xl'
|
|
9
|
+
|
|
10
|
+
/** @deprecated Use `variant` with `size`: `headingSm` is `variant="heading" size="sm"`. */
|
|
11
|
+
export type HeadingLegacyVariant =
|
|
7
12
|
| 'displayLg'
|
|
8
13
|
| 'displayMd'
|
|
9
14
|
| 'displaySm'
|
|
@@ -12,9 +17,14 @@ export type HeadingVariant =
|
|
|
12
17
|
| 'headingMd'
|
|
13
18
|
| 'headingSm'
|
|
14
19
|
|
|
20
|
+
export type HeadingVariant = HeadingFamily | HeadingLegacyVariant
|
|
21
|
+
|
|
15
22
|
interface HeadingProps {
|
|
16
23
|
level: 1 | 2 | 3 | 4 | 5 | 6
|
|
24
|
+
/** The look: `heading` (default), `display` (fluid hero titles), `overline` (small uppercase group titles). */
|
|
17
25
|
variant?: HeadingVariant
|
|
26
|
+
/** The size within the look. Default: the level's size for `heading`, `md` for `display`, `sm` for `overline`; `display` and `overline` stop at `lg`. */
|
|
27
|
+
size?: HeadingSize
|
|
18
28
|
color?: 'default' | 'muted' | 'accent'
|
|
19
29
|
align?: 'start' | 'center' | 'end'
|
|
20
30
|
/** The heading's id: an in-page link's target (`href="#id"`), an `aria-labelledby` reference. */
|
|
@@ -22,13 +32,32 @@ interface HeadingProps {
|
|
|
22
32
|
children: ReactNode
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
type Preset = keyof typeof typePresets
|
|
36
|
+
|
|
37
|
+
const levelSize: Record<HeadingProps['level'], HeadingSize> = {
|
|
38
|
+
1: 'xl',
|
|
39
|
+
2: 'lg',
|
|
40
|
+
3: 'md',
|
|
41
|
+
4: 'sm',
|
|
42
|
+
5: 'sm',
|
|
43
|
+
6: 'sm',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const presets = {
|
|
47
|
+
heading: {sm: 'headingSm', md: 'headingMd', lg: 'headingLg', xl: 'headingXl'},
|
|
48
|
+
display: {sm: 'displaySm', md: 'displayMd', lg: 'displayLg'},
|
|
49
|
+
overline: {sm: 'overline', md: 'overlineMd', lg: 'overlineLg'},
|
|
50
|
+
} as const satisfies Record<HeadingFamily, Partial<Record<HeadingSize, Preset>>>
|
|
51
|
+
|
|
52
|
+
function presetOf(
|
|
53
|
+
level: HeadingProps['level'],
|
|
54
|
+
variant: HeadingVariant = 'heading',
|
|
55
|
+
size?: HeadingSize,
|
|
56
|
+
): Preset {
|
|
57
|
+
if (variant === 'heading') return presets.heading[size ?? levelSize[level]]
|
|
58
|
+
if (variant === 'display') return presets.display[size === 'xl' ? 'lg' : (size ?? 'md')]
|
|
59
|
+
if (variant === 'overline') return presets.overline[size === 'xl' ? 'lg' : (size ?? 'sm')]
|
|
60
|
+
return variant
|
|
32
61
|
}
|
|
33
62
|
|
|
34
63
|
const alignMap = {
|
|
@@ -37,9 +66,20 @@ const alignMap = {
|
|
|
37
66
|
end: styles.alignEnd,
|
|
38
67
|
} as const
|
|
39
68
|
|
|
40
|
-
export function Heading({
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
export function Heading({
|
|
70
|
+
level,
|
|
71
|
+
variant,
|
|
72
|
+
size,
|
|
73
|
+
color = 'default',
|
|
74
|
+
align,
|
|
75
|
+
id,
|
|
76
|
+
children,
|
|
77
|
+
}: HeadingProps) {
|
|
78
|
+
const style = [
|
|
79
|
+
typePresets[presetOf(level, variant, size)],
|
|
80
|
+
styles[color],
|
|
81
|
+
align && alignMap[align],
|
|
82
|
+
]
|
|
43
83
|
|
|
44
84
|
if (level === 1)
|
|
45
85
|
return (
|
|
@@ -6,10 +6,10 @@ export const meta: ComponentMeta = {
|
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Body text (paragraphs, descriptions)',
|
|
8
8
|
'UI labels and metadata',
|
|
9
|
-
'Captions, code snippets,
|
|
9
|
+
'Captions, code snippets, and overline eyebrows above a Heading',
|
|
10
10
|
],
|
|
11
11
|
whenNotToUse: [
|
|
12
|
-
'Page or
|
|
12
|
+
'Page, section or group titles — use Heading, even in overline style (variant="overline")',
|
|
13
13
|
'Form field labels — use Field.Label (auto-binds to form)',
|
|
14
14
|
],
|
|
15
15
|
relatedTo: [
|
package/src/docs/Typography.mdx
CHANGED
|
@@ -55,12 +55,15 @@ Presets compose scale tokens + weight + tracking into named styles via `typePres
|
|
|
55
55
|
|
|
56
56
|
### Heading
|
|
57
57
|
|
|
58
|
-
| Preset | Size | Weight | Tracking
|
|
59
|
-
| ----------- | ----- | -------- |
|
|
60
|
-
| `headingSm` | 20/28 | Semibold | Tight
|
|
61
|
-
| `headingMd` | 24/32 | Semibold | Tight
|
|
62
|
-
| `headingLg` | 30/36 | Bold | Tight
|
|
63
|
-
| `headingXl` | 36/44 | Bold | Tight
|
|
58
|
+
| Preset | Size | Weight | Tracking |
|
|
59
|
+
| ----------- | ----- | -------- | -------------------------------------------------------- |
|
|
60
|
+
| `headingSm` | 20/28 | Semibold | Tight |
|
|
61
|
+
| `headingMd` | 24/32 | Semibold | Tight |
|
|
62
|
+
| `headingLg` | 30/36 | Bold | Tight |
|
|
63
|
+
| `headingXl` | 36/44 | Bold | Tight |
|
|
64
|
+
| `overline` | 12/16 | Semibold | Wide, uppercase (`overlineMd` 14/20, `overlineLg` 16/24) |
|
|
65
|
+
|
|
66
|
+
`Heading` picks a preset from two props: `variant` (`heading`, `display`, `overline`) and `size` (`sm`, `md`, `lg`, `xl`). `<Heading level={3} variant="overline" size="lg">` is a group title at 16/24. Without `size`, a heading takes its level's size, a display title `md` and an overline `sm`.
|
|
64
67
|
|
|
65
68
|
### Display (fluid)
|
|
66
69
|
|
|
@@ -82,12 +85,13 @@ See the **Fluid Display** story for a resizable demo.
|
|
|
82
85
|
|
|
83
86
|
## When to use which layer
|
|
84
87
|
|
|
85
|
-
| Scenario | Use
|
|
86
|
-
| ---------------------------------------------- |
|
|
87
|
-
| Body text in a page or card | `<Text variant="bodyMd">`
|
|
88
|
-
| Section heading | `<Heading level={2}>`
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
88
|
+
| Scenario | Use |
|
|
89
|
+
| ---------------------------------------------- | ------------------------------------------------- |
|
|
90
|
+
| Body text in a page or card | `<Text variant="bodyMd">` |
|
|
91
|
+
| Section heading | `<Heading level={2}>` |
|
|
92
|
+
| Group title over a list of rows | `<Heading level={3} variant="overline">` |
|
|
93
|
+
| Hero or landing page title | `<Heading level={1} variant="display" size="lg">` |
|
|
94
|
+
| Form label | `<Text variant="label">` |
|
|
95
|
+
| Inline code | `<Text variant="code">` |
|
|
96
|
+
| Custom component needing raw font size | `typeScale.fontSize4` token |
|
|
97
|
+
| Composing a one-off style with preset defaults | `typePresets.bodyMd` in `css.create()` |
|
package/src/index.native.ts
CHANGED
|
@@ -20,7 +20,7 @@ export {Badge, type BadgeVariant, type BadgeSize} from './components/Badge/Badge
|
|
|
20
20
|
export {Button, type ButtonVariant, type ButtonSize} from './components/Button/Button'
|
|
21
21
|
export {Card, type CardVariant, type CardSize} from './components/Card/Card'
|
|
22
22
|
export {Checkbox} from './components/Checkbox/Checkbox'
|
|
23
|
-
export {Heading, type HeadingVariant} from './components/Heading/Heading'
|
|
23
|
+
export {Heading, type HeadingSize, type HeadingVariant} from './components/Heading/Heading'
|
|
24
24
|
export {Input, type InputVariant} from './components/Input/Input'
|
|
25
25
|
export {Switch} from './components/Switch/Switch'
|
|
26
26
|
export {Text, type TextVariant, type TextColor} from './components/Text/Text'
|
package/src/index.ts
CHANGED
|
@@ -56,7 +56,7 @@ export {
|
|
|
56
56
|
type SnapPoint,
|
|
57
57
|
} from './components/Drawer/Drawer'
|
|
58
58
|
export {EmptyState} from './components/EmptyState/EmptyState'
|
|
59
|
-
export {Heading, type HeadingVariant} from './components/Heading/Heading'
|
|
59
|
+
export {Heading, type HeadingSize, type HeadingVariant} from './components/Heading/Heading'
|
|
60
60
|
export {List, type ListSelectionMode} from './components/List/List'
|
|
61
61
|
export {LiveRegion, type LiveRegionPoliteness} from './components/LiveRegion/LiveRegion'
|
|
62
62
|
export {Field} from './components/Field/Field'
|