@maestro-js/components 1.0.0-alpha.0 → 1.0.0-alpha.10

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 (55) hide show
  1. package/commands/add.ts +16 -1
  2. package/dist/components.json +1 -1
  3. package/dist/index.js +14 -1
  4. package/package.json +5 -5
  5. package/registry.json +610 -802
  6. package/scripts/build.ts +11 -4
  7. package/src/components/alert-dialog.tsx +3 -3
  8. package/src/components/avatar.tsx +1 -1
  9. package/src/components/button-link.tsx +3 -0
  10. package/src/components/button.tsx +4 -0
  11. package/src/components/checkbox.tsx +7 -7
  12. package/src/components/chip.tsx +7 -7
  13. package/src/components/container.tsx +3 -3
  14. package/src/components/date-range.tsx +161 -0
  15. package/src/components/dialog.tsx +21 -7
  16. package/src/components/disclosure.tsx +1 -1
  17. package/src/components/drawer.tsx +19 -5
  18. package/src/components/form.tsx +27 -5
  19. package/src/components/helpers/form-field.tsx +7 -8
  20. package/src/components/helpers/get-button-classes.ts +23 -23
  21. package/src/components/helpers/headless-button.tsx +1 -1
  22. package/src/components/inline-alert.tsx +3 -3
  23. package/src/components/labeled-value.tsx +2 -2
  24. package/src/components/menu.tsx +60 -26
  25. package/src/components/month-day-input.tsx +2 -1
  26. package/src/components/multiselect.tsx +74 -70
  27. package/src/components/optional-link.tsx +15 -0
  28. package/src/components/radio.tsx +8 -8
  29. package/src/components/select.tsx +9 -8
  30. package/src/components/stepper.tsx +6 -6
  31. package/src/components/switch.tsx +7 -7
  32. package/src/components/table/headless-templated-row-table.ts +550 -0
  33. package/src/components/table/index.tsx +41 -0
  34. package/src/components/table/server-table.ts +143 -0
  35. package/src/components/table/table-actions.tsx +76 -0
  36. package/src/components/table/table-container.tsx +173 -0
  37. package/src/components/table/table-container.type-test.ts +155 -0
  38. package/src/components/table/table-filters/date-range-filter.tsx +227 -0
  39. package/src/components/table/table-filters/select-filter.tsx +211 -0
  40. package/src/components/table/table-filters/sort.tsx +85 -0
  41. package/src/components/table/table-primitives.tsx +226 -0
  42. package/src/components/table/table-toolbar.tsx +300 -0
  43. package/src/components/table/table-types.ts +61 -0
  44. package/src/components/table/use-client-table.ts +157 -0
  45. package/src/components/table/use-client-table.type-test.ts +217 -0
  46. package/src/components/table/use-server-table.ts +192 -0
  47. package/src/components/table/use-server-table.type-test.ts +174 -0
  48. package/src/components/tabs.tsx +1 -1
  49. package/src/components/tag-field.tsx +3 -3
  50. package/src/components/text-area.tsx +1 -1
  51. package/src/components/toast.tsx +6 -6
  52. package/src/components/toggle-button-group.tsx +2 -2
  53. package/src/utils/icons.d.ts +2 -1
  54. package/src/utils/use-query-state.ts +626 -0
  55. package/src/utils/use-tab-indicator.ts +9 -9
@@ -0,0 +1,174 @@
1
+ import { useServerTable } from './use-server-table'
2
+ import type { ServerTable } from './server-table'
3
+ import type { TableLocalization } from './table-types'
4
+ import type { Iso } from 'iso-fns'
5
+
6
+ // =============================================================================
7
+ // Helpers
8
+ // =============================================================================
9
+
10
+ type Expect<T extends true> = T
11
+ type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
12
+
13
+ // =============================================================================
14
+ // Setup
15
+ // =============================================================================
16
+
17
+ type Row = {
18
+ id: string
19
+ status: 'active' | 'inactive'
20
+ name: string
21
+ createdAt: string
22
+ }
23
+
24
+ // =============================================================================
25
+ // Filter and sort config inference
26
+ // =============================================================================
27
+
28
+ function testFilterAndSortInference() {
29
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, { status: 'active' | 'inactive' }, { newest: true; oldest: true }>
30
+
31
+ const table = useServerTable(serverTable, { getRowId: (row) => row.id })
32
+
33
+ // getFilterConfig() keys match the provided filter names
34
+ type _FilterKeys = Expect<Equal<keyof ReturnType<typeof table.getFilterConfig>, 'status'>>
35
+
36
+ // getSortConfig() keys match the provided sort names
37
+ type _SortKeys = Expect<Equal<keyof ReturnType<typeof table.getSortConfig>, 'newest' | 'oldest'>>
38
+
39
+ // state.sort is the union of sort keys | null
40
+ type _SortState = Expect<Equal<typeof table.state.sort, 'newest' | 'oldest' | null>>
41
+
42
+ // state.filters keys match filter names
43
+ type _FilterStateKeys = Expect<Equal<keyof typeof table.state.filters, 'status'>>
44
+ }
45
+
46
+ // =============================================================================
47
+ // Localization compatibility — select filter
48
+ // =============================================================================
49
+
50
+ function testLocalizationWithSelectFilter() {
51
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, { status: 'active' | 'inactive' }, { newest: true; oldest: true }>
52
+
53
+ const table = useServerTable(serverTable, { getRowId: (row) => row.id })
54
+
55
+ type Loc = TableLocalization<
56
+ ReturnType<typeof table.getFilterConfig>,
57
+ ReturnType<typeof table.getSortConfig>
58
+ >
59
+
60
+ type _LocFilterKeys = Expect<Equal<keyof Loc['filters'], 'status'>>
61
+ type _LocSortKeys = Expect<Equal<keyof Loc['sorts'], 'newest' | 'oldest'>>
62
+
63
+ // Valid localization with typed option values
64
+ const _loc: Loc = {
65
+ filters: {
66
+ status: {
67
+ label: 'Status',
68
+ options: [
69
+ { label: 'Active', value: 'active' },
70
+ { label: 'Inactive', value: 'inactive' }
71
+ ]
72
+ }
73
+ },
74
+ sorts: {
75
+ newest: { label: 'Newest' },
76
+ oldest: { label: 'Oldest' }
77
+ }
78
+ }
79
+ }
80
+
81
+ // =============================================================================
82
+ // Localization compatibility — date range filter
83
+ // =============================================================================
84
+
85
+ function testLocalizationWithDateRangeFilter() {
86
+ type RowWithDate = {
87
+ id: string
88
+ dateRange: { startDate: Iso.Date; endDate: Iso.Date }
89
+ }
90
+
91
+ const serverTable = null as unknown as ServerTable.ServerTable<
92
+ RowWithDate,
93
+ { dateRange: { startDate: Iso.Date; endDate: Iso.Date } },
94
+ {}
95
+ >
96
+
97
+ const table = useServerTable(serverTable, { getRowId: (row) => row.id })
98
+
99
+ type Loc = TableLocalization<
100
+ ReturnType<typeof table.getFilterConfig>,
101
+ ReturnType<typeof table.getSortConfig>
102
+ >
103
+
104
+ // Valid: date-range filter accepts DateRangeTableFilter form
105
+ const _loc: Loc = {
106
+ filters: {
107
+ dateRange: { label: 'Date Range', type: 'date-range' }
108
+ },
109
+ sorts: {}
110
+ }
111
+
112
+ // Invalid: date-range filter rejects select-style options
113
+ const _badLoc: Loc = {
114
+ filters: {
115
+ dateRange: {
116
+ label: 'Date Range',
117
+ type: 'date-range',
118
+ // @ts-expect-error — select-style options array is not valid for date-range
119
+ options: [{ label: 'X', value: 'x' }]
120
+ }
121
+ },
122
+ sorts: {}
123
+ }
124
+ }
125
+
126
+ // =============================================================================
127
+ // Negative: wrong localization option value
128
+ // =============================================================================
129
+
130
+ declare function checkLocalization(l: TableLocalization<{ status: 'active' | 'inactive' }, {}>): void
131
+
132
+ function testWrongLocalizationOptionValue() {
133
+ checkLocalization({
134
+ filters: {
135
+ status: {
136
+ label: 'Status',
137
+ // @ts-expect-error — 'unknown' is not assignable to 'active' | 'inactive'
138
+ options: [{ label: 'Unknown', value: 'unknown' }]
139
+ }
140
+ },
141
+ sorts: {}
142
+ })
143
+ }
144
+
145
+ // =============================================================================
146
+ // No filters/sorts → empty localization
147
+ // =============================================================================
148
+
149
+ function testNoFiltersOrSorts() {
150
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, {}, {}>
151
+
152
+ const table = useServerTable(serverTable, { getRowId: (row) => row.id })
153
+
154
+ type Loc = TableLocalization<
155
+ ReturnType<typeof table.getFilterConfig>,
156
+ ReturnType<typeof table.getSortConfig>
157
+ >
158
+
159
+ type _EmptyFilters = Expect<Equal<Loc['filters'], {}>>
160
+ type _EmptySorts = Expect<Equal<Loc['sorts'], {}>>
161
+ }
162
+
163
+ // =============================================================================
164
+ // State shape — search and pagination
165
+ // =============================================================================
166
+
167
+ function testStateShape() {
168
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, { status: 'active' | 'inactive' }, { newest: true }>
169
+
170
+ const table = useServerTable(serverTable, { getRowId: (row) => row.id })
171
+
172
+ type _Search = Expect<Equal<typeof table.state.search, string>>
173
+ type _Pagination = Expect<Equal<typeof table.state.pagination, { limit: number; skip: number }>>
174
+ }
@@ -96,7 +96,7 @@ TabList.displayName = 'TabList'
96
96
  function TabItem({ children, className, ...props }: TabProps) {
97
97
  const { color, variant } = React.useContext(TabListContext)
98
98
  const colors = tabColorMap[color]
99
- const focusClasses = `outline-none data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[focus-visible]:${colors.outline}`
99
+ const focusClasses = `outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:${colors.outline}`
100
100
 
101
101
  return (
102
102
  <AriaTab
@@ -226,7 +226,7 @@ function Tag({
226
226
  return (
227
227
  <span
228
228
  className={tw(
229
- 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-normal bg-primary-500/10 text-primary-500',
229
+ 'inline-flex items-center rounded-full pl-2 pr-1.5 py-0.5 text-xs font-normal bg-primary-500/10 text-primary-500',
230
230
  isDisabled && 'opacity-50'
231
231
  )}
232
232
  >
@@ -238,11 +238,11 @@ function Tag({
238
238
  e.stopPropagation()
239
239
  onRemove()
240
240
  }}
241
- className="inline-flex items-center justify-center h-4 w-4 -m-0.5 p-0.5 rounded-full translate-x-1 hover:bg-black/10"
241
+ className="inline-flex items-center justify-center size-4 -m-0.5 p-0.5 rounded-full translate-x-1 hover:bg-neutral-950/10"
242
242
  aria-label="Remove"
243
243
  tabIndex={-1}
244
244
  >
245
- <Icon name="x-mark" className="h-2 w-2" aria-hidden="true" />
245
+ <Icon name="x-mark" className="size-2" aria-hidden="true" />
246
246
  </button>
247
247
  )}
248
248
  </span>
@@ -113,7 +113,7 @@ export function TextArea(props: TextAreaProps) {
113
113
  ref={textAreaRef}
114
114
  id={textAreaId}
115
115
  className={tw(
116
- 'w-full bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm text-neutral-900 placeholder:text-neutral-400',
116
+ 'w-full bg-transparent px-3 py-2 border-0 focus:ring-0 focus:outline-none text-sm max-sm:text-base text-neutral-900 placeholder:text-neutral-400',
117
117
  autoResize && 'resize-none overflow-hidden col-span-full row-span-full',
118
118
  (props.isDisabled || props.isReadonly) && 'cursor-not-allowed',
119
119
  !autoResize && props.resize === 'none' && 'resize-none',
@@ -99,7 +99,7 @@ function ToastItem({ toast }: { toast: { content: Toast; key: string } }) {
99
99
  style={{ viewTransitionName: toast.key } as React.CSSProperties}
100
100
  className={tw(
101
101
  'pointer-events-auto',
102
- 'flex gap-3 rounded-md border px-4 py-3 shadow-lg',
102
+ 'flex gap-3 rounded-lg px-4 py-3 shadow-lg',
103
103
  'min-w-[300px] max-w-md',
104
104
  '[view-transition-class:toast]',
105
105
  hasDescription ? 'items-start' : 'items-center',
@@ -152,11 +152,11 @@ function DeclaredToast({
152
152
  // --- Variant Maps ---
153
153
 
154
154
  const variantClasses = {
155
- info: 'bg-info-500/10 text-info-500 border-info-500/20',
156
- positive: 'bg-positive-50 text-positive-800 border-positive-200',
157
- warning: 'bg-notice-50 text-notice-800 border-notice-200',
158
- negative: 'bg-negative-50 text-negative-800 border-negative-200',
159
- notice: 'bg-neutral-100 text-neutral-700 border-neutral-200'
155
+ info: 'bg-info-50 text-info-800 ring-1 ring-info-200',
156
+ positive: 'bg-positive-50 text-positive-800 ring-1 ring-positive-200',
157
+ warning: 'bg-notice-50 text-notice-800 ring-1 ring-notice-200',
158
+ negative: 'bg-negative-50 text-negative-800 ring-1 ring-negative-200',
159
+ notice: 'bg-neutral-50 text-neutral-700 ring-1 ring-neutral-200'
160
160
  }
161
161
 
162
162
  const variantIconNames = {
@@ -82,12 +82,12 @@ function getToggleButtonClasses({ size = 'md', isSelected }: { size?: ButtonSize
82
82
  // Unselected
83
83
  !isSelected && 'bg-white ring-1 ring-inset ring-neutral-300',
84
84
  !isSelected && 'data-[hovered]:bg-neutral-50 data-[hovered]:ring-neutral-400',
85
- !isSelected && 'text-neutral-700 data-[focus-visible]:outline-neutral-400',
85
+ !isSelected && 'text-neutral-700 data-[focus-visible]:outline-primary-600',
86
86
 
87
87
  // Selected
88
88
  isSelected && 'bg-primary-500 text-white ring-1 ring-inset ring-primary-700',
89
89
  isSelected && 'data-[hovered]:bg-primary-600 data-[hovered]:ring-primary-700',
90
- isSelected && 'data-[focus-visible]:outline-primary-500',
90
+ isSelected && 'data-[focus-visible]:outline-primary-600',
91
91
 
92
92
  'inline-flex justify-center items-center gap-x-2'
93
93
  )
@@ -13,7 +13,8 @@ declare module '~/icons/types' {
13
13
  'chevron-down',
14
14
  'exclamation-triangle',
15
15
  'minus',
16
- 'arrow-path'
16
+ 'arrow-path',
17
+ 'magnifying-glass'
17
18
  ] as const
18
19
 
19
20
  export type IconName = (typeof iconNames)[number]