@duro-app/ui 5.5.0 → 5.7.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.
Files changed (115) hide show
  1. package/dist/{Table-DYN-bp0e.js → Table-Dj7PXqO_.js} +405 -387
  2. package/dist/Table-Dj7PXqO_.js.map +1 -0
  3. package/dist/components/Button/Button.d.ts +2 -1
  4. package/dist/components/Button/Button.d.ts.map +1 -1
  5. package/dist/components/Button/styles.css.d.ts +1 -0
  6. package/dist/components/Button/styles.css.d.ts.map +1 -1
  7. package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts +14 -0
  8. package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts.map +1 -1
  9. package/dist/components/DragDrop/DragDrop.d.ts +95 -14
  10. package/dist/components/DragDrop/DragDrop.d.ts.map +1 -1
  11. package/dist/components/DragDrop/DragDrop.meta.d.ts.map +1 -1
  12. package/dist/components/DragDrop/DragDropBoard.stories.d.ts +69 -0
  13. package/dist/components/DragDrop/DragDropBoard.stories.d.ts.map +1 -0
  14. package/dist/components/DragDrop/styles.css.d.ts +80 -3
  15. package/dist/components/DragDrop/styles.css.d.ts.map +1 -1
  16. package/dist/components/LinkButton/styles.css.d.ts +1 -0
  17. package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
  18. package/dist/components/Menu/Menu.d.ts +4 -1
  19. package/dist/components/Menu/Menu.d.ts.map +1 -1
  20. package/dist/components/Menu/styles.css.d.ts +14 -0
  21. package/dist/components/Menu/styles.css.d.ts.map +1 -1
  22. package/dist/components/Meter/Meter.d.ts +17 -0
  23. package/dist/components/Meter/Meter.d.ts.map +1 -0
  24. package/dist/components/Meter/Meter.meta.d.ts +3 -0
  25. package/dist/components/Meter/Meter.meta.d.ts.map +1 -0
  26. package/dist/components/Meter/Meter.stories.d.ts +12 -0
  27. package/dist/components/Meter/Meter.stories.d.ts.map +1 -0
  28. package/dist/components/Meter/styles.css.d.ts +34 -0
  29. package/dist/components/Meter/styles.css.d.ts.map +1 -0
  30. package/dist/components/Popover/Popover.d.ts +14 -2
  31. package/dist/components/Popover/Popover.d.ts.map +1 -1
  32. package/dist/components/Popover/Popover.meta.d.ts.map +1 -1
  33. package/dist/components/Popover/PopoverLayerContext.d.ts +3 -0
  34. package/dist/components/Popover/PopoverLayerContext.d.ts.map +1 -1
  35. package/dist/components/Popover/PopoverRaised.stories.d.ts +28 -0
  36. package/dist/components/Popover/PopoverRaised.stories.d.ts.map +1 -0
  37. package/dist/components/Popover/styles.css.d.ts +15 -0
  38. package/dist/components/Popover/styles.css.d.ts.map +1 -1
  39. package/dist/components/Select/Select.d.ts +4 -1
  40. package/dist/components/Select/Select.d.ts.map +1 -1
  41. package/dist/components/Select/styles.css.d.ts +9 -0
  42. package/dist/components/Select/styles.css.d.ts.map +1 -1
  43. package/dist/components/Timeline/Timeline.d.ts +71 -0
  44. package/dist/components/Timeline/Timeline.d.ts.map +1 -0
  45. package/dist/components/Timeline/Timeline.meta.d.ts +3 -0
  46. package/dist/components/Timeline/Timeline.meta.d.ts.map +1 -0
  47. package/dist/components/Timeline/Timeline.stories.d.ts +26 -0
  48. package/dist/components/Timeline/Timeline.stories.d.ts.map +1 -0
  49. package/dist/components/Timeline/styles.css.d.ts +158 -0
  50. package/dist/components/Timeline/styles.css.d.ts.map +1 -0
  51. package/dist/components/Toast/Toast.meta.d.ts.map +1 -1
  52. package/dist/components/Toast/Toast.stories.d.ts +9 -0
  53. package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
  54. package/dist/components/Toast/ToastProvider.d.ts +11 -5
  55. package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
  56. package/dist/components/Toast/index.d.ts +1 -1
  57. package/dist/components/Toast/index.d.ts.map +1 -1
  58. package/dist/components/Toast/styles.css.d.ts +6 -0
  59. package/dist/components/Toast/styles.css.d.ts.map +1 -1
  60. package/dist/components/Toggle/Toggle.d.ts +2 -1
  61. package/dist/components/Toggle/Toggle.d.ts.map +1 -1
  62. package/dist/components/Toggle/styles.css.d.ts +1 -0
  63. package/dist/components/Toggle/styles.css.d.ts.map +1 -1
  64. package/dist/index.css +1 -1
  65. package/dist/index.d.ts +6 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4992 -3845
  68. package/dist/index.js.map +1 -1
  69. package/dist/shared/ModalContext.d.ts +7 -0
  70. package/dist/shared/ModalContext.d.ts.map +1 -1
  71. package/dist/shared/exports.typecheck.d.ts +9 -0
  72. package/dist/shared/exports.typecheck.d.ts.map +1 -0
  73. package/dist/shared/types.d.ts +2 -0
  74. package/dist/shared/types.d.ts.map +1 -1
  75. package/dist/table.js +1 -1
  76. package/package.json +2 -2
  77. package/src/components/Button/Button.tsx +2 -1
  78. package/src/components/Button/styles.css.ts +3 -0
  79. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +172 -14
  80. package/src/components/DragDrop/DragDrop.meta.ts +22 -21
  81. package/src/components/DragDrop/DragDrop.tsx +1147 -246
  82. package/src/components/DragDrop/DragDropBoard.stories.tsx +668 -0
  83. package/src/components/DragDrop/styles.css.ts +74 -4
  84. package/src/components/LinkButton/styles.css.ts +4 -0
  85. package/src/components/Menu/Menu.meta.ts +1 -1
  86. package/src/components/Menu/Menu.tsx +17 -2
  87. package/src/components/Menu/styles.css.ts +18 -0
  88. package/src/components/Meter/Meter.meta.ts +24 -0
  89. package/src/components/Meter/Meter.stories.tsx +64 -0
  90. package/src/components/Meter/Meter.tsx +55 -0
  91. package/src/components/Meter/styles.css.ts +30 -0
  92. package/src/components/Popover/Popover.meta.ts +2 -1
  93. package/src/components/Popover/Popover.tsx +29 -4
  94. package/src/components/Popover/PopoverLayerContext.ts +3 -0
  95. package/src/components/Popover/PopoverRaised.stories.tsx +334 -0
  96. package/src/components/Popover/styles.css.ts +21 -0
  97. package/src/components/Select/Select.meta.ts +1 -1
  98. package/src/components/Select/Select.tsx +5 -1
  99. package/src/components/Select/styles.css.ts +12 -0
  100. package/src/components/Timeline/Timeline.meta.ts +46 -0
  101. package/src/components/Timeline/Timeline.stories.tsx +238 -0
  102. package/src/components/Timeline/Timeline.tsx +332 -0
  103. package/src/components/Timeline/styles.css.ts +162 -0
  104. package/src/components/Toast/Toast.meta.ts +2 -1
  105. package/src/components/Toast/Toast.stories.tsx +84 -0
  106. package/src/components/Toast/ToastProvider.tsx +49 -11
  107. package/src/components/Toast/index.ts +7 -1
  108. package/src/components/Toast/styles.css.ts +2 -0
  109. package/src/components/Toggle/Toggle.tsx +2 -1
  110. package/src/components/Toggle/styles.css.ts +2 -0
  111. package/src/index.ts +18 -0
  112. package/src/shared/ModalContext.ts +8 -1
  113. package/src/shared/exports.typecheck.ts +16 -0
  114. package/src/shared/types.ts +3 -0
  115. package/dist/Table-DYN-bp0e.js.map +0 -1
@@ -0,0 +1,162 @@
1
+ import {css} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {microSpacing, radii, spacing} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
5
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
8
+
9
+ export const styles = css.create({
10
+ // Two columns — row labels, date track — with the axis as the first row.
11
+ root: {
12
+ display: 'grid',
13
+ gridTemplateColumns: `${sizes.timelineLabelW} 1fr`,
14
+ rowGap: spacing.xs,
15
+ color: colors.text,
16
+ },
17
+ heading: {
18
+ display: 'flex',
19
+ alignItems: 'flex-end',
20
+ fontSize: typography.fontSizeXs,
21
+ color: colors.textMuted,
22
+ },
23
+ axis: {
24
+ position: 'relative',
25
+ height: sizes.chip,
26
+ fontSize: typography.fontSizeXs,
27
+ color: colors.textMuted,
28
+ whiteSpace: 'nowrap',
29
+ },
30
+ tick: {
31
+ position: 'absolute',
32
+ top: 0,
33
+ paddingLeft: spacing.xs,
34
+ borderLeftWidth: borders.hairline,
35
+ borderLeftStyle: 'solid',
36
+ borderLeftColor: colors.border,
37
+ },
38
+ todayLabel: {
39
+ position: 'absolute',
40
+ bottom: 0,
41
+ transform: 'translateX(-50%)',
42
+ color: colors.errorText,
43
+ fontWeight: typography.fontWeightMedium,
44
+ },
45
+ left: (percent: number) => ({left: `${percent}%`}),
46
+ width: (percent: number) => ({width: `${percent}%`}),
47
+ label: {
48
+ display: 'flex',
49
+ flexDirection: 'column',
50
+ justifyContent: 'center',
51
+ minWidth: 0,
52
+ paddingRight: spacing.sm,
53
+ },
54
+ labelText: {
55
+ fontSize: typography.fontSizeSm,
56
+ fontWeight: typography.fontWeightMedium,
57
+ whiteSpace: 'nowrap',
58
+ overflow: 'hidden',
59
+ textOverflow: 'ellipsis',
60
+ },
61
+ labelDescription: {
62
+ fontSize: typography.fontSizeXs,
63
+ color: colors.textMuted,
64
+ whiteSpace: 'nowrap',
65
+ },
66
+ track: {
67
+ position: 'relative',
68
+ height: sizes.controlMd,
69
+ },
70
+ today: {
71
+ position: 'absolute',
72
+ top: 0,
73
+ bottom: 0,
74
+ borderLeftWidth: borders.strong,
75
+ borderLeftStyle: 'solid',
76
+ borderLeftColor: colors.error,
77
+ pointerEvents: 'none',
78
+ zIndex: 2,
79
+ },
80
+ bar: {
81
+ position: 'absolute',
82
+ top: '50%',
83
+ transform: 'translateY(-50%)',
84
+ boxSizing: 'border-box',
85
+ height: sizes.controlSm,
86
+ },
87
+ face: {
88
+ position: 'relative',
89
+ display: 'flex',
90
+ alignItems: 'center',
91
+ boxSizing: 'border-box',
92
+ width: '100%',
93
+ height: '100%',
94
+ padding: 0,
95
+ overflow: 'hidden',
96
+ borderRadius: radii.xs,
97
+ borderWidth: borders.hairline,
98
+ borderStyle: 'solid',
99
+ borderColor: {default: colors.border, ':hover': colors.accent},
100
+ backgroundColor: colors.bg,
101
+ color: colors.text,
102
+ cursor: 'pointer',
103
+ textAlign: 'left',
104
+ transitionProperty: 'box-shadow, border-color',
105
+ transitionDuration: duration.fast,
106
+ transitionTimingFunction: easing.standard,
107
+ outlineStyle: {default: 'none', ':focus-visible': 'solid'},
108
+ outlineWidth: borders.focusRing,
109
+ outlineColor: colors.accent,
110
+ outlineOffset: borders.focusOffset,
111
+ },
112
+ faceSelected: {
113
+ borderColor: colors.accent,
114
+ boxShadow: `0 0 0 ${microSpacing.px1} ${colors.accent}`,
115
+ },
116
+ barPlanned: {
117
+ borderStyle: 'dashed',
118
+ },
119
+ barDone: {
120
+ backgroundColor: colors.successBg,
121
+ },
122
+ fill: {
123
+ position: 'absolute',
124
+ left: 0,
125
+ top: 0,
126
+ bottom: 0,
127
+ backgroundColor: colors.infoBg,
128
+ borderRightWidth: borders.strong,
129
+ borderRightStyle: 'solid',
130
+ borderRightColor: colors.accent,
131
+ },
132
+ fillDone: {
133
+ backgroundColor: colors.successBg,
134
+ borderRightColor: colors.success,
135
+ },
136
+ barText: {
137
+ position: 'relative',
138
+ paddingLeft: spacing.sm,
139
+ fontSize: typography.fontSizeXs,
140
+ fontVariantNumeric: 'tabular-nums',
141
+ whiteSpace: 'nowrap',
142
+ },
143
+ // The edges: keyboard sliders, drawn only while focused.
144
+ edge: {
145
+ position: 'absolute',
146
+ top: 0,
147
+ bottom: 0,
148
+ width: spacing.sm,
149
+ borderRadius: radii.xs,
150
+ outlineStyle: {default: 'none', ':focus-visible': 'solid'},
151
+ outlineWidth: borders.focusRing,
152
+ outlineColor: colors.accent,
153
+ backgroundColor: {default: 'transparent', ':focus-visible': colors.accent},
154
+ zIndex: 3,
155
+ },
156
+ edgeStart: {
157
+ left: `calc(-1 * ${spacing.xs})`,
158
+ },
159
+ edgeEnd: {
160
+ right: `calc(-1 * ${spacing.xs})`,
161
+ },
162
+ })
@@ -2,11 +2,12 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs.',
5
+ 'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).',
6
6
  whenToUse: [
7
7
  'Confirming an action succeeded (e.g. "Grant revoked")',
8
8
  'Reporting an action failed, with the reason',
9
9
  'Reversible actions that benefit from an inline Undo',
10
+ 'A soft limit crossed but allowed (variant warning), e.g. a column over its WIP limit',
10
11
  ],
11
12
  whenNotToUse: [
12
13
  'In-form validation errors — use Field.Error or an inline Alert',
@@ -47,6 +47,31 @@ function Triggers() {
47
47
  >
48
48
  Show with undo
49
49
  </html.button>
50
+ <html.button
51
+ style={s.btn}
52
+ onClick={() =>
53
+ toast({
54
+ variant: 'warning',
55
+ message: 'In progress is over its WIP limit (4/3)',
56
+ duration: 0,
57
+ })
58
+ }
59
+ >
60
+ Show warning
61
+ </html.button>
62
+ <html.button
63
+ style={s.btn}
64
+ onClick={() =>
65
+ toast({
66
+ variant: 'success',
67
+ message: 'Moved to Done',
68
+ duration: 600,
69
+ action: {label: 'Undo', onClick: () => {}},
70
+ })
71
+ }
72
+ >
73
+ Show short
74
+ </html.button>
50
75
  </html.div>
51
76
  )
52
77
  }
@@ -87,3 +112,62 @@ export const Dismiss: Story = {
87
112
  await waitFor(() => expect(canvas.queryByRole('status')).not.toBeInTheDocument())
88
113
  },
89
114
  }
115
+
116
+ /** The WIP-limit shape: a warning, announced politely, with its own tone. */
117
+ export const Warning: Story = {
118
+ play: async ({canvas}) => {
119
+ await userEvent.click(canvas.getByText('Show warning'))
120
+ const toast = await waitFor(() => canvas.getByRole('status'))
121
+ await expect(toast).toHaveTextContent('In progress is over its WIP limit (4/3)')
122
+ },
123
+ }
124
+
125
+ const wait = (ms: number) => new Promise((r) => setTimeout(r, ms))
126
+
127
+ /** The countdown stops while the pointer is on the toast and resumes with
128
+ * what was left when it leaves. */
129
+ export const PausesOnHover: Story = {
130
+ play: async ({canvas}) => {
131
+ await userEvent.click(canvas.getByText('Show short'))
132
+ const toast = await waitFor(() => canvas.getByRole('status'))
133
+ await userEvent.hover(toast)
134
+ await wait(1000)
135
+ await expect(canvas.getByRole('status')).toHaveTextContent('Moved to Done')
136
+ await userEvent.unhover(toast)
137
+ await waitFor(() => expect(canvas.queryByRole('status')).not.toBeInTheDocument(), {
138
+ timeout: 2000,
139
+ })
140
+ },
141
+ }
142
+
143
+ /** Focus inside the toast (its Undo, its close button) holds it too. */
144
+ export const PausesOnFocus: Story = {
145
+ play: async ({canvas}) => {
146
+ await userEvent.click(canvas.getByText('Show short'))
147
+ await waitFor(() => canvas.getByRole('status'))
148
+ canvas.getByRole('button', {name: 'Undo'}).focus()
149
+ await wait(1000)
150
+ // Moving between the toast's own buttons keeps it paused.
151
+ canvas.getByRole('button', {name: 'Dismiss'}).focus()
152
+ await wait(300)
153
+ await expect(canvas.getByRole('status')).toHaveTextContent('Moved to Done')
154
+ canvas.getByText('Show short').focus()
155
+ await waitFor(() => expect(canvas.queryByText('Moved to Done')).not.toBeInTheDocument(), {
156
+ timeout: 2000,
157
+ })
158
+ },
159
+ }
160
+
161
+ /** The close button's name comes from the app (dismissLabel). */
162
+ export const LocalisedClose: Story = {
163
+ render: () => (
164
+ <ToastProvider dismissLabel="Fermer">
165
+ <Triggers />
166
+ </ToastProvider>
167
+ ),
168
+ play: async ({canvas}) => {
169
+ await userEvent.click(canvas.getByText('Show warning'))
170
+ await userEvent.click(await canvas.findByRole('button', {name: 'Fermer'}))
171
+ await waitFor(() => expect(canvas.queryByRole('status')).not.toBeInTheDocument())
172
+ },
173
+ }
@@ -5,6 +5,7 @@ import {
5
5
  useEffect,
6
6
  useMemo,
7
7
  useRef,
8
+ useState,
8
9
  useSyncExternalStore,
9
10
  type ReactNode,
10
11
  } from 'react'
@@ -14,7 +15,7 @@ import {Icon, type IconName} from '../Icon'
14
15
  import {usePortalMount} from '../ThemeProvider/ThemeProvider'
15
16
  import {styles} from './styles.css'
16
17
 
17
- export type ToastVariant = 'success' | 'error' | 'info'
18
+ export type ToastVariant = 'success' | 'error' | 'info' | 'warning'
18
19
 
19
20
  export interface ToastAction {
20
21
  label: string
@@ -22,12 +23,14 @@ export interface ToastAction {
22
23
  }
23
24
 
24
25
  export interface ToastOptions {
25
- /** Visual + semantic tone. Errors announce assertively and linger longer. */
26
+ /** Visual + semantic tone. Errors announce assertively; errors and
27
+ * warnings linger longer. */
26
28
  variant?: ToastVariant
27
29
  message: ReactNode
28
30
  /** Optional single action, e.g. Undo. Runs, then dismisses the toast. */
29
31
  action?: ToastAction
30
- /** Auto-dismiss delay in ms. `0` (or negative) keeps it until dismissed. */
32
+ /** Auto-dismiss delay in ms. `0` (or negative) keeps it until dismissed.
33
+ * The countdown pauses while the toast is hovered or holds focus. */
31
34
  duration?: number
32
35
  }
33
36
 
@@ -56,6 +59,7 @@ const DEFAULT_DURATION: Record<ToastVariant, number> = {
56
59
  success: 5000,
57
60
  info: 5000,
58
61
  error: 8000,
62
+ warning: 8000,
59
63
  }
60
64
 
61
65
  function createToastStore(): ToastStore {
@@ -92,11 +96,13 @@ const variantIcon: Record<ToastVariant, IconName> = {
92
96
  success: 'check-circle-filled',
93
97
  error: 'x-circle-filled',
94
98
  info: 'info-circle-filled',
99
+ warning: 'alert-triangle-filled',
95
100
  }
96
101
  const variantIconStyle = {
97
102
  success: styles.iconSuccess,
98
103
  error: styles.iconError,
99
104
  info: styles.iconInfo,
105
+ warning: styles.iconWarning,
100
106
  } as const
101
107
 
102
108
  const ToastContext = createContext<ToastContextValue | null>(null)
@@ -107,20 +113,45 @@ export function useToast(): ToastContextValue {
107
113
  return ctx
108
114
  }
109
115
 
110
- function ToastItem({entry, onDismiss}: {entry: ToastEntry; onDismiss: (id: string) => void}) {
116
+ function ToastItem({
117
+ entry,
118
+ onDismiss,
119
+ dismissLabel,
120
+ }: {
121
+ entry: ToastEntry
122
+ onDismiss: (id: string) => void
123
+ dismissLabel: string
124
+ }) {
111
125
  const {id, variant, message, action, duration} = entry
126
+ const [hovered, setHovered] = useState(false)
127
+ const [focused, setFocused] = useState(false)
128
+ const paused = hovered || focused
129
+ // What is left of the countdown; a pause keeps it, a resume restarts the
130
+ // timer with what was left (WCAG 2.2.1: the reader controls the timing).
131
+ const remaining = useRef(duration)
112
132
 
113
133
  useEffect(() => {
114
- if (duration <= 0) return
115
- const timer = setTimeout(() => onDismiss(id), duration)
116
- return () => clearTimeout(timer)
117
- }, [id, duration, onDismiss])
134
+ if (duration <= 0 || paused) return
135
+ const started = Date.now()
136
+ const timer = setTimeout(() => onDismiss(id), Math.max(remaining.current, 0))
137
+ return () => {
138
+ clearTimeout(timer)
139
+ remaining.current -= Date.now() - started
140
+ }
141
+ }, [id, duration, paused, onDismiss])
118
142
 
119
143
  return (
120
144
  <html.div
121
145
  role={variant === 'error' ? 'alert' : 'status'}
122
146
  aria-live={variant === 'error' ? 'assertive' : 'polite'}
123
147
  style={[styles.toast, styles[variant]]}
148
+ onPointerEnter={() => setHovered(true)}
149
+ onPointerLeave={() => setHovered(false)}
150
+ onFocus={() => setFocused(true)}
151
+ onBlur={(e: {currentTarget: HTMLElement; relatedTarget: EventTarget | null}) => {
152
+ // Focus moving between the toast's own buttons keeps it paused.
153
+ if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocused(false)
154
+ }}
124
155
  >
125
156
  <html.div style={[styles.iconWrap, variantIconStyle[variant]]}>
126
157
  <Icon name={variantIcon[variant]} size="md" />
@@ -142,7 +173,7 @@ function ToastItem({entry, onDismiss}: {entry: ToastEntry; onDismiss: (id: strin
142
173
  </html.div>
143
174
  <html.button
144
175
  type="button"
145
- aria-label="Dismiss"
176
+ aria-label={dismissLabel}
146
177
  style={styles.closeBtn}
147
178
  onClick={() => onDismiss(id)}
148
179
  >
@@ -152,7 +183,14 @@ function ToastItem({entry, onDismiss}: {entry: ToastEntry; onDismiss: (id: strin
152
183
  )
153
184
  }
154
185
 
155
- export function ToastProvider({children}: {children: ReactNode}) {
186
+ export interface ToastProviderProps {
187
+ /** Accessible name of each toast's close button, e.g. from the app's
188
+ * message catalog. */
189
+ dismissLabel?: string
190
+ children: ReactNode
191
+ }
192
+
193
+ export function ToastProvider({dismissLabel = 'Dismiss', children}: ToastProviderProps) {
156
194
  const storeRef = useRef<ToastStore>(null)
157
195
  if (storeRef.current === null) {
158
196
  storeRef.current = createToastStore()
@@ -175,7 +213,7 @@ export function ToastProvider({children}: {children: ReactNode}) {
175
213
  const region = (
176
214
  <html.div role="region" aria-label="Notifications" style={styles.region}>
177
215
  {entries.map((entry) => (
178
- <ToastItem key={entry.id} entry={entry} onDismiss={dismiss} />
216
+ <ToastItem key={entry.id} entry={entry} onDismiss={dismiss} dismissLabel={dismissLabel} />
179
217
  ))}
180
218
  </html.div>
181
219
  )
@@ -1,2 +1,8 @@
1
1
  export {ToastProvider, useToast} from './ToastProvider'
2
- export type {ToastVariant, ToastOptions, ToastAction, ToastContextValue} from './ToastProvider'
2
+ export type {
3
+ ToastVariant,
4
+ ToastOptions,
5
+ ToastAction,
6
+ ToastContextValue,
7
+ ToastProviderProps,
8
+ } from './ToastProvider'
@@ -62,6 +62,7 @@ export const styles = css.create({
62
62
  success: {borderLeftColor: colors.successBorder},
63
63
  error: {borderLeftColor: colors.errorBorder},
64
64
  info: {borderLeftColor: colors.infoBorder},
65
+ warning: {borderLeftColor: colors.warningBorder},
65
66
 
66
67
  iconWrap: {
67
68
  flexShrink: 0,
@@ -73,6 +74,7 @@ export const styles = css.create({
73
74
  iconSuccess: {color: colors.successText},
74
75
  iconError: {color: colors.errorText},
75
76
  iconInfo: {color: colors.infoText},
77
+ iconWarning: {color: colors.warningText},
76
78
 
77
79
  content: {
78
80
  flex: 1,
@@ -4,9 +4,10 @@ import {useControllableValue} from '../../hooks/useControllableValue'
4
4
  import {useToggleGroup} from '../ToggleGroup/ToggleGroupContext'
5
5
  import {useGroupedControl} from '../ButtonGroup/useGroupedControl'
6
6
  import {mergeRefs} from '../../shared/mergeRefs'
7
+ import type {ControlSize} from '../../shared/types'
7
8
  import {styles} from './styles.css'
8
9
 
9
- export type ToggleSize = 'default' | 'small'
10
+ export type ToggleSize = ControlSize
10
11
 
11
12
  interface ToggleProps {
12
13
  /** Controlled pressed state (standalone usage). */
@@ -37,6 +37,8 @@ export const styles = css.create({
37
37
  fontSize: typography.fontSizeXs,
38
38
  borderRadius: radii.sm,
39
39
  gap: spacing.xs,
40
+ // Every small control is at least controlSm tall, whatever it holds (5.6).
41
+ minHeight: {default: sizes.controlSm, '@media (pointer: coarse)': sizes.touchTarget},
40
42
  },
41
43
  // wrapped toggles: a fixed block size per size, the touch target under a
42
44
  // coarse pointer. ToggleGroup's maxRows arithmetic reads the same tokens.
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export {Icon, type IconName} from './components/Icon'
14
14
  export {Badge, type BadgeVariant, type BadgeSize} from './components/Badge/Badge'
15
15
  export {Breadcrumb} from './components/Breadcrumb/Breadcrumb'
16
16
  export {Button, type ButtonVariant, type ButtonSize} from './components/Button/Button'
17
+ export type {ControlSize} from './shared/types'
17
18
  export {ButtonGroup, type ButtonGroupProps} from './components/ButtonGroup/ButtonGroup'
18
19
  export {Callout, type CalloutVariant} from './components/Callout/Callout'
19
20
  export {Card, type CardVariant, type CardSize} from './components/Card/Card'
@@ -24,9 +25,15 @@ export {ConfirmDialog} from './components/ConfirmDialog/ConfirmDialog'
24
25
  export {
25
26
  DragDrop,
26
27
  useDragDrop,
28
+ type DragDropAnnouncement,
27
29
  type DragDropEvent,
28
30
  type DragDropItemData,
31
+ type DragDropOverEvent,
32
+ type DragDropPlaceholderInfo,
33
+ type DragDropRefusal,
34
+ type DragDropRenderPlaceholder,
29
35
  type DragDropTarget,
36
+ type DragDropVerdict,
30
37
  } from './components/DragDrop/DragDrop'
31
38
  export {Dialog, type DialogSize} from './components/Dialog/Dialog'
32
39
  export {DetailPanel, type DetailPanelSize} from './components/DetailPanel/DetailPanel'
@@ -45,6 +52,7 @@ export {
45
52
  type LinkButtonSize,
46
53
  } from './components/LinkButton/LinkButton'
47
54
  export {Menu} from './components/Menu/Menu'
55
+ export {Meter, type MeterProps, type MeterTone} from './components/Meter/Meter'
48
56
  export {PageNav} from './components/PageNav/PageNav'
49
57
  export {Panel} from './components/Panel/Panel'
50
58
  export {RadioGroup} from './components/RadioGroup/RadioGroup'
@@ -64,6 +72,7 @@ export {
64
72
  type ToastOptions,
65
73
  type ToastAction,
66
74
  type ToastContextValue,
75
+ type ToastProviderProps,
67
76
  } from './components/Toast'
68
77
  export {
69
78
  StatusIcon,
@@ -83,6 +92,14 @@ export {
83
92
  type TableOfContentsVariant,
84
93
  } from './components/TableOfContents/TableOfContents'
85
94
  export {Tabs} from './components/Tabs/Tabs'
95
+ export {
96
+ Timeline,
97
+ type TimelineBarDropZone,
98
+ type TimelineBarProps,
99
+ type TimelineDate,
100
+ type TimelineRootProps,
101
+ type TimelineRowProps,
102
+ } from './components/Timeline/Timeline'
86
103
  export {Tree} from './components/Tree/Tree'
87
104
  export {Textarea, type TextareaVariant} from './components/Textarea/Textarea'
88
105
  export {
@@ -103,6 +120,7 @@ export {Tooltip} from './components/Tooltip/Tooltip'
103
120
  export {Toolbar, type ToolbarProps} from './components/Toolbar/Toolbar'
104
121
  export {Popover} from './components/Popover/Popover'
105
122
  export type {PopoverHandle} from './components/Popover/Popover'
123
+ export {useInModal} from './shared/ModalContext'
106
124
  export {Switch} from './components/Switch/Switch'
107
125
 
108
126
  // Layout primitives
@@ -13,6 +13,13 @@ import {createContext, useContext} from 'react'
13
13
  */
14
14
  export const ModalContext = createContext(false)
15
15
 
16
- export function useInModal() {
16
+ /**
17
+ * True inside a Dialog or Drawer, false outside. A floating surface that is not
18
+ * a Duro Popover picks its layer with it, the way a Popover does:
19
+ * `useInModal() ? layers.popover : layers.floating`, and for one that must
20
+ * cover another floating surface (a link editor over a format bar)
21
+ * `useInModal() ? layers.popoverRaised : layers.floatingRaised`.
22
+ */
23
+ export function useInModal(): boolean {
17
24
  return useContext(ModalContext)
18
25
  }
@@ -0,0 +1,16 @@
1
+ // Compile-only checks that 5.6's additions are on the package root, where a
2
+ // consumer imports them. `pnpm typecheck` fails if one is dropped from
3
+ // index.ts or changes shape. Never imported at runtime.
4
+
5
+ import type {ButtonSize, ControlSize, ToggleSize, useInModal} from '../index'
6
+
7
+ type Same<T, U> = [T] extends [U] ? ([U] extends [T] ? true : false) : false
8
+
9
+ // A floating surface that is not a Popover picks its layer with it.
10
+ export const useInModalIsBoolean: Same<ReturnType<typeof useInModal>, boolean> = true
11
+ export const useInModalTakesNothing: Same<Parameters<typeof useInModal>, []> = true
12
+
13
+ // The shared size, and the two sizes that alias it.
14
+ export const controlSizes: Same<ControlSize, 'default' | 'small'> = true
15
+ export const buttonSizeIsControlSize: Same<ButtonSize, ControlSize> = true
16
+ export const toggleSizeIsControlSize: Same<ToggleSize, ControlSize> = true
@@ -1,3 +1,6 @@
1
1
  // Cross-component prop types that don't belong to any single component.
2
2
 
3
3
  export type Orientation = 'horizontal' | 'vertical'
4
+
5
+ /** The two control sizes shared by Button, Toggle and the Select, Menu and Popover triggers. */
6
+ export type ControlSize = 'default' | 'small'