@duro-app/ui 0.23.0 → 0.25.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 (100) hide show
  1. package/dist/components/ButtonGroup/ButtonGroup.d.ts +14 -0
  2. package/dist/components/ButtonGroup/ButtonGroup.d.ts.map +1 -0
  3. package/dist/components/ButtonGroup/ButtonGroup.meta.d.ts +3 -0
  4. package/dist/components/ButtonGroup/ButtonGroup.meta.d.ts.map +1 -0
  5. package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts +11 -0
  6. package/dist/components/ButtonGroup/ButtonGroup.stories.d.ts.map +1 -0
  7. package/dist/components/ButtonGroup/styles.css.d.ts +45 -0
  8. package/dist/components/ButtonGroup/styles.css.d.ts.map +1 -0
  9. package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
  10. package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts.map +1 -1
  11. package/dist/components/Combobox/Combobox.d.ts +44 -0
  12. package/dist/components/Combobox/Combobox.d.ts.map +1 -0
  13. package/dist/components/Combobox/Combobox.meta.d.ts +3 -0
  14. package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -0
  15. package/dist/components/Combobox/Combobox.stories.d.ts +9 -0
  16. package/dist/components/Combobox/Combobox.stories.d.ts.map +1 -0
  17. package/dist/components/Combobox/ComboboxContext.d.ts +19 -0
  18. package/dist/components/Combobox/ComboboxContext.d.ts.map +1 -0
  19. package/dist/components/Combobox/styles.css.d.ts +112 -0
  20. package/dist/components/Combobox/styles.css.d.ts.map +1 -0
  21. package/dist/components/Combobox/useComboboxRoot.d.ts +15 -0
  22. package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -0
  23. package/dist/components/Field/Field.d.ts +4 -0
  24. package/dist/components/Field/Field.d.ts.map +1 -1
  25. package/dist/components/Field/Field.stories.d.ts +3 -0
  26. package/dist/components/Field/Field.stories.d.ts.map +1 -1
  27. package/dist/components/Field/FieldContext.d.ts +5 -0
  28. package/dist/components/Field/FieldContext.d.ts.map +1 -1
  29. package/dist/components/Field/styles.css.d.ts +26 -0
  30. package/dist/components/Field/styles.css.d.ts.map +1 -1
  31. package/dist/components/Form/Form.d.ts +7 -2
  32. package/dist/components/Form/Form.d.ts.map +1 -1
  33. package/dist/components/Form/Form.stories.d.ts +6 -0
  34. package/dist/components/Form/Form.stories.d.ts.map +1 -1
  35. package/dist/components/Form/FormContext.d.ts +9 -2
  36. package/dist/components/Form/FormContext.d.ts.map +1 -1
  37. package/dist/components/Input/Input.d.ts.map +1 -1
  38. package/dist/components/List/List.d.ts.map +1 -1
  39. package/dist/components/List/List.meta.d.ts.map +1 -1
  40. package/dist/components/List/List.stories.d.ts.map +1 -1
  41. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  42. package/dist/components/RadioGroup/RadioGroup.meta.d.ts.map +1 -1
  43. package/dist/components/Table/ColumnFilter.d.ts +8 -0
  44. package/dist/components/Table/ColumnFilter.d.ts.map +1 -0
  45. package/dist/components/Table/Pagination.d.ts +7 -0
  46. package/dist/components/Table/Pagination.d.ts.map +1 -0
  47. package/dist/components/Table/SortIndicator.d.ts +16 -0
  48. package/dist/components/Table/SortIndicator.d.ts.map +1 -0
  49. package/dist/components/Table/Table.d.ts +7 -1
  50. package/dist/components/Table/Table.d.ts.map +1 -1
  51. package/dist/components/Table/Table.stories.d.ts +4 -0
  52. package/dist/components/Table/Table.stories.d.ts.map +1 -1
  53. package/dist/components/Table/styles.css.d.ts +11 -3
  54. package/dist/components/Table/styles.css.d.ts.map +1 -1
  55. package/dist/components/Table/useDataTable.d.ts +32 -0
  56. package/dist/components/Table/useDataTable.d.ts.map +1 -0
  57. package/dist/components/Textarea/Textarea.d.ts.map +1 -1
  58. package/dist/index.css +1 -1
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +2863 -2026
  62. package/dist/index.js.map +1 -1
  63. package/package.json +3 -2
  64. package/src/components/ActionBar/ActionBar.stories.tsx +3 -3
  65. package/src/components/ButtonGroup/ButtonGroup.meta.ts +32 -0
  66. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +103 -0
  67. package/src/components/ButtonGroup/ButtonGroup.tsx +59 -0
  68. package/src/components/ButtonGroup/styles.css.ts +28 -0
  69. package/src/components/CheckboxGroup/CheckboxGroup.meta.ts +4 -1
  70. package/src/components/CheckboxGroup/CheckboxGroup.tsx +2 -9
  71. package/src/components/Combobox/Combobox.meta.ts +35 -0
  72. package/src/components/Combobox/Combobox.stories.tsx +119 -0
  73. package/src/components/Combobox/Combobox.tsx +247 -0
  74. package/src/components/Combobox/ComboboxContext.ts +26 -0
  75. package/src/components/Combobox/styles.css.ts +119 -0
  76. package/src/components/Combobox/useComboboxRoot.ts +235 -0
  77. package/src/components/Field/Field.stories.tsx +60 -0
  78. package/src/components/Field/Field.tsx +97 -9
  79. package/src/components/Field/FieldContext.ts +5 -0
  80. package/src/components/Field/styles.css.ts +26 -0
  81. package/src/components/Form/Form.stories.tsx +275 -0
  82. package/src/components/Form/Form.tsx +73 -7
  83. package/src/components/Form/FormContext.ts +10 -1
  84. package/src/components/Input/Input.tsx +2 -1
  85. package/src/components/List/List.meta.ts +8 -2
  86. package/src/components/List/List.stories.tsx +35 -15
  87. package/src/components/List/List.tsx +1 -5
  88. package/src/components/RadioGroup/RadioGroup.meta.ts +4 -1
  89. package/src/components/RadioGroup/RadioGroup.tsx +1 -4
  90. package/src/components/Table/ColumnFilter.tsx +46 -0
  91. package/src/components/Table/Pagination.tsx +82 -0
  92. package/src/components/Table/SortIndicator.tsx +53 -0
  93. package/src/components/Table/Table.stories.tsx +397 -0
  94. package/src/components/Table/Table.tsx +58 -24
  95. package/src/components/Table/styles.css.ts +16 -6
  96. package/src/components/Table/useDataTable.ts +99 -0
  97. package/src/components/Textarea/Textarea.tsx +2 -1
  98. package/src/docs/helpers.tsx +2 -2
  99. package/src/index.ts +5 -0
  100. package/src/strict.css +5 -0
@@ -18,9 +18,15 @@ export const meta: ComponentMeta = {
18
18
  optional: ['Description', 'Actions', 'Empty'],
19
19
  },
20
20
  relatedTo: [
21
- {component: 'Table', relationship: 'Table for wide multi-column data; List for narrow compact items'},
21
+ {
22
+ component: 'Table',
23
+ relationship: 'Table for wide multi-column data; List for narrow compact items',
24
+ },
22
25
  {component: 'DetailPanel', relationship: 'List fits naturally inside DetailPanel.Body'},
23
- {component: 'Menu', relationship: 'Menu for action menus; List for data display with selection'},
26
+ {
27
+ component: 'Menu',
28
+ relationship: 'Menu for action menus; List for data display with selection',
29
+ },
24
30
  ],
25
31
  example: `<List.Root selectionMode="multiple" aria-label="Certificates">
26
32
  <List.Item selected onClick={toggle}>
@@ -32,7 +32,9 @@ export const Default: Story = {
32
32
  <List.Description>Reverse proxy · Port 443</List.Description>
33
33
  </List.Content>
34
34
  <List.Actions>
35
- <Badge variant="success" size="sm">Running</Badge>
35
+ <Badge variant="success" size="sm">
36
+ Running
37
+ </Badge>
36
38
  </List.Actions>
37
39
  </List.Item>
38
40
  <List.Item>
@@ -41,7 +43,9 @@ export const Default: Story = {
41
43
  <List.Description>Web server · Port 80</List.Description>
42
44
  </List.Content>
43
45
  <List.Actions>
44
- <Badge variant="success" size="sm">Running</Badge>
46
+ <Badge variant="success" size="sm">
47
+ Running
48
+ </Badge>
45
49
  </List.Actions>
46
50
  </List.Item>
47
51
  <List.Item>
@@ -50,7 +54,9 @@ export const Default: Story = {
50
54
  <List.Description>DNS filter · Port 53</List.Description>
51
55
  </List.Content>
52
56
  <List.Actions>
53
- <Badge variant="default" size="sm">Stopped</Badge>
57
+ <Badge variant="default" size="sm">
58
+ Stopped
59
+ </Badge>
54
60
  </List.Actions>
55
61
  </List.Item>
56
62
  </List.Root>
@@ -77,9 +83,27 @@ const narrowContainer = css.create({
77
83
 
78
84
  function SelectionDemo() {
79
85
  const certs = [
80
- {id: 'c1', serial: 'aa:bb:cc:dd:01', issued: 'Mar 21, 2026', expires: 'Jun 19, 2026', status: 'active' as const},
81
- {id: 'c2', serial: 'aa:bb:cc:dd:02', issued: 'Mar 21, 2026', expires: 'Jun 19, 2026', status: 'active' as const},
82
- {id: 'c3', serial: 'ee:ff:00:11:03', issued: 'Jan 10, 2025', expires: 'Jan 10, 2026', status: 'expired' as const},
86
+ {
87
+ id: 'c1',
88
+ serial: 'aa:bb:cc:dd:01',
89
+ issued: 'Mar 21, 2026',
90
+ expires: 'Jun 19, 2026',
91
+ status: 'active' as const,
92
+ },
93
+ {
94
+ id: 'c2',
95
+ serial: 'aa:bb:cc:dd:02',
96
+ issued: 'Mar 21, 2026',
97
+ expires: 'Jun 19, 2026',
98
+ status: 'active' as const,
99
+ },
100
+ {
101
+ id: 'c3',
102
+ serial: 'ee:ff:00:11:03',
103
+ issued: 'Jan 10, 2025',
104
+ expires: 'Jan 10, 2026',
105
+ status: 'expired' as const,
106
+ },
83
107
  ]
84
108
 
85
109
  const [selected, setSelected] = useState<Set<string>>(new Set())
@@ -117,10 +141,7 @@ function SelectionDemo() {
117
141
  </List.Description>
118
142
  </List.Content>
119
143
  <List.Actions>
120
- <Badge
121
- variant={cert.status === 'active' ? 'success' : 'default'}
122
- size="sm"
123
- >
144
+ <Badge variant={cert.status === 'active' ? 'success' : 'default'} size="sm">
124
145
  {cert.status}
125
146
  </Badge>
126
147
  {cert.status === 'active' && (
@@ -178,13 +199,12 @@ function DetailPanelDemo() {
178
199
  >
179
200
  <List.Content>
180
201
  <List.Text>{user.name}</List.Text>
181
- <List.Description>{user.email} · {user.role}</List.Description>
202
+ <List.Description>
203
+ {user.email} · {user.role}
204
+ </List.Description>
182
205
  </List.Content>
183
206
  <List.Actions>
184
- <Badge
185
- variant={user.status === 'Active' ? 'success' : 'default'}
186
- size="sm"
187
- >
207
+ <Badge variant={user.status === 'Active' ? 'success' : 'default'} size="sm">
188
208
  {user.status}
189
209
  </Badge>
190
210
  </List.Actions>
@@ -60,11 +60,7 @@ function Item({children, selected, disabled, onClick}: ItemProps) {
60
60
  aria-selected={isSelectable ? selected : undefined}
61
61
  aria-disabled={disabled || undefined}
62
62
  onClick={disabled ? undefined : onClick}
63
- style={[
64
- styles.item,
65
- selected && styles.itemSelected,
66
- disabled && styles.itemDisabled,
67
- ]}
63
+ style={[styles.item, selected && styles.itemSelected, disabled && styles.itemDisabled]}
68
64
  >
69
65
  {children}
70
66
  </html.div>
@@ -13,7 +13,10 @@ export const meta: ComponentMeta = {
13
13
  'Binary on/off — use Switch',
14
14
  ],
15
15
  relatedTo: [
16
- {component: 'CheckboxGroup', relationship: 'CheckboxGroup for multi-select; RadioGroup for single-select'},
16
+ {
17
+ component: 'CheckboxGroup',
18
+ relationship: 'CheckboxGroup for multi-select; RadioGroup for single-select',
19
+ },
17
20
  {component: 'Select', relationship: 'Select for larger option sets or when space is limited'},
18
21
  {component: 'ToggleGroup', relationship: 'ToggleGroup for toolbar-style selection'},
19
22
  ],
@@ -84,10 +84,7 @@ function Item({value, disabled: itemDisabled = false, children}: ItemProps) {
84
84
  }
85
85
 
86
86
  return (
87
- <html.label
88
- style={[styles.item, isDisabled && styles.itemDisabled]}
89
- onClick={handleClick}
90
- >
87
+ <html.label style={[styles.item, isDisabled && styles.itemDisabled]} onClick={handleClick}>
91
88
  <html.input
92
89
  type="radio"
93
90
  value={value}
@@ -0,0 +1,46 @@
1
+ import type {Column} from '@tanstack/react-table'
2
+ import {html} from 'react-strict-dom'
3
+ import {css} from 'react-strict-dom'
4
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
5
+ import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
6
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
+
8
+ const styles = css.create({
9
+ input: {
10
+ width: '100%',
11
+ paddingTop: 2,
12
+ paddingBottom: 2,
13
+ paddingLeft: spacing.xs,
14
+ paddingRight: spacing.xs,
15
+ marginTop: spacing.xs,
16
+ borderWidth: 1,
17
+ borderStyle: 'solid',
18
+ borderColor: colors.border,
19
+ borderRadius: radii.xs,
20
+ backgroundColor: colors.bg,
21
+ color: colors.text,
22
+ fontSize: typography.fontSizeXs,
23
+ outline: 'none',
24
+ },
25
+ })
26
+
27
+ interface ColumnFilterProps<TData> {
28
+ column: Column<TData, unknown>
29
+ placeholder?: string
30
+ }
31
+
32
+ export function ColumnFilter<TData>({column, placeholder}: ColumnFilterProps<TData>) {
33
+ if (!column.getCanFilter()) return null
34
+
35
+ const filterValue = (column.getFilterValue() as string) ?? ''
36
+
37
+ return (
38
+ <html.input
39
+ style={styles.input}
40
+ type="text"
41
+ value={filterValue}
42
+ onChange={(e: any) => column.setFilterValue(e.target.value || undefined)}
43
+ placeholder={placeholder ?? `Filter...`}
44
+ />
45
+ )
46
+ }
@@ -0,0 +1,82 @@
1
+ import type {Table} from '@tanstack/react-table'
2
+ import {html} from 'react-strict-dom'
3
+ import {css} from 'react-strict-dom'
4
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
5
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
6
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
7
+
8
+ const styles = css.create({
9
+ root: {
10
+ display: 'flex',
11
+ alignItems: 'center',
12
+ justifyContent: 'center',
13
+ gap: spacing.sm,
14
+ paddingTop: spacing.md,
15
+ paddingBottom: spacing.sm,
16
+ },
17
+ button: {
18
+ display: 'inline-flex',
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ paddingTop: spacing.xs,
22
+ paddingBottom: spacing.xs,
23
+ paddingLeft: spacing.sm,
24
+ paddingRight: spacing.sm,
25
+ borderWidth: 1,
26
+ borderStyle: 'solid',
27
+ borderColor: colors.border,
28
+ borderRadius: 6,
29
+ backgroundColor: {
30
+ default: 'transparent',
31
+ ':hover': colors.bgCardHover,
32
+ },
33
+ color: colors.text,
34
+ fontSize: typography.fontSizeSm,
35
+ cursor: 'pointer',
36
+ transitionProperty: 'background-color',
37
+ transitionDuration: '150ms',
38
+ },
39
+ buttonDisabled: {
40
+ opacity: 0.4,
41
+ cursor: 'not-allowed',
42
+ backgroundColor: 'transparent',
43
+ },
44
+ pageInfo: {
45
+ fontSize: typography.fontSizeSm,
46
+ color: colors.textMuted,
47
+ },
48
+ })
49
+
50
+ interface PaginationProps<TData> {
51
+ table: Table<TData>
52
+ }
53
+
54
+ export function Pagination<TData>({table}: PaginationProps<TData>) {
55
+ const {pageIndex, pageSize} = table.getState().pagination
56
+ const pageCount = table.getPageCount()
57
+ const totalRows = table.getFilteredRowModel().rows.length
58
+
59
+ if (totalRows <= pageSize) return null
60
+
61
+ return (
62
+ <html.div style={styles.root}>
63
+ <html.button
64
+ style={[styles.button, !table.getCanPreviousPage() && styles.buttonDisabled]}
65
+ onClick={() => table.previousPage()}
66
+ disabled={!table.getCanPreviousPage()}
67
+ >
68
+ Previous
69
+ </html.button>
70
+ <html.span style={styles.pageInfo}>
71
+ {pageIndex + 1} / {pageCount}
72
+ </html.span>
73
+ <html.button
74
+ style={[styles.button, !table.getCanNextPage() && styles.buttonDisabled]}
75
+ onClick={() => table.nextPage()}
76
+ disabled={!table.getCanNextPage()}
77
+ >
78
+ Next
79
+ </html.button>
80
+ </html.div>
81
+ )
82
+ }
@@ -0,0 +1,53 @@
1
+ import type {Column} from '@tanstack/react-table'
2
+ import {html} from 'react-strict-dom'
3
+ import {css} from 'react-strict-dom'
4
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
5
+
6
+ const styles = css.create({
7
+ root: {
8
+ display: 'inline-flex',
9
+ alignItems: 'center',
10
+ marginLeft: 4,
11
+ fontSize: 10,
12
+ userSelect: 'none',
13
+ // Hidden by default, shown on hover or when active
14
+ opacity: 0,
15
+ transitionProperty: 'opacity',
16
+ transitionDuration: '120ms',
17
+ },
18
+ active: {
19
+ opacity: 1,
20
+ color: colors.accent,
21
+ },
22
+ inactive: {
23
+ color: colors.textMuted,
24
+ },
25
+ })
26
+
27
+ interface SortIndicatorProps<TData> {
28
+ column: Column<TData, unknown>
29
+ }
30
+
31
+ /**
32
+ * Sort direction chevron for table column headers.
33
+ *
34
+ * - Shows \u25B2 (asc), \u25BC (desc), or \u21C5 (unsorted)
35
+ * - Hidden by default, visible on header hover or when actively sorted
36
+ * - Clicking the header cell should call column.getToggleSortingHandler()
37
+ *
38
+ * The parent HeaderCell applies a :hover rule that sets child opacity to 1.
39
+ */
40
+ export function SortIndicator<TData>({column}: SortIndicatorProps<TData>) {
41
+ if (!column.getCanSort()) return null
42
+
43
+ const sorted = column.getIsSorted()
44
+
45
+ return (
46
+ <html.span
47
+ style={[styles.root, sorted ? styles.active : styles.inactive]}
48
+ data-sort-indicator=""
49
+ >
50
+ {sorted === 'asc' ? '\u25B2' : sorted === 'desc' ? '\u25BC' : '\u21C5'}
51
+ </html.span>
52
+ )
53
+ }