@maestro-js/components 1.0.0-alpha.22 → 1.0.0-alpha.3

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/package.json CHANGED
@@ -6,9 +6,9 @@
6
6
  "@types/node": "^22.19.11",
7
7
  "@types/react": "^19.1.8",
8
8
  "@types/react-dom": "^19.1.6",
9
- "react-pdf-hook": "^1.0.4",
9
+ "react-pdf-hook": "^1.0.3",
10
10
  "vaul": "^1.1.2",
11
- "@maestro-js/form": "1.0.0-alpha.22"
11
+ "@maestro-js/form": "1.0.0-alpha.3"
12
12
  },
13
13
  "peerDependencies": {
14
14
  "@bundled-es-modules/pdfjs-dist": "3.6.172-alpha.1",
@@ -22,13 +22,13 @@
22
22
  "pdfjs-dist": ">=5",
23
23
  "react": ">=19",
24
24
  "react-dom": ">=19",
25
- "react-pdf-hook": "^1.0.4",
25
+ "react-pdf-hook": "^1.0.3",
26
26
  "react-router": ">=7",
27
27
  "vite-env-only": "^3.0.2",
28
28
  "vaul": ">=1",
29
- "@maestro-js/form": "1.0.0-alpha.22"
29
+ "@maestro-js/form": "1.0.0-alpha.3"
30
30
  },
31
- "version": "1.0.0-alpha.22",
31
+ "version": "1.0.0-alpha.3",
32
32
  "license": "UNLICENSED",
33
33
  "engines": {
34
34
  "node": ">=22.18.0"
@@ -100,10 +100,7 @@ function AlertDialog_({
100
100
  'data-[exiting]:scale-95 data-[exiting]:opacity-0 data-[exiting]:translate-y-2'
101
101
  )}
102
102
  >
103
- <Dialog
104
- className={tw('outline-none bg-white rounded-xl shadow-xl ring-1 ring-black/5 w-full', className)}
105
- role="alertdialog"
106
- >
103
+ <Dialog className={tw('outline-none bg-white rounded-xl shadow-xl ring-1 ring-black/5 w-full', className)} role="alertdialog">
107
104
  {({ close }) => (
108
105
  <AlertDialogContext.Provider value={{ close }}>
109
106
  <ButtonGroupContext.Provider
@@ -48,7 +48,7 @@ function stringAvatar(name: string) {
48
48
 
49
49
  if (!first) return ''
50
50
 
51
- const initials = names.length === 1 ? (first[0] ?? '') : `${first[0] ?? ''}${last?.[0] ?? ''}`
51
+ const initials = names.length === 1 ? first[0] ?? '' : `${first[0] ?? ''}${last?.[0] ?? ''}`
52
52
 
53
53
  return initials.toUpperCase()
54
54
  }
@@ -13,7 +13,6 @@ import {
13
13
  import { twMerge } from 'tailwind-merge'
14
14
  import { parseDate, type CalendarDate, type DateValue } from '@internationalized/date'
15
15
  import type { Iso } from 'iso-fns'
16
- import { Icon } from './icon'
17
16
 
18
17
  export type DateRangeRootAdditionalProps = {
19
18
  defaultStart: Iso.Date | null
@@ -75,11 +74,11 @@ export function DateRangePicker(props: DateRangePickerProps) {
75
74
  >
76
75
  <header className="flex items-center justify-between mb-2 px-1">
77
76
  <Button slot="previous" className="p-1 hover:bg-neutral-100 rounded cursor-pointer">
78
- <Icon name="chevron-left" />
77
+ <ChevronLeft />
79
78
  </Button>
80
79
  <Heading className="text-sm font-semibold text-neutral-900 flex-1 text-center" />
81
80
  <Button slot="next" className="p-1 hover:bg-neutral-100 rounded cursor-pointer">
82
- <Icon name="chevron-right" />
81
+ <ChevronRight />
83
82
  </Button>
84
83
  </header>
85
84
  <div className="flex gap-4">
@@ -136,3 +135,27 @@ export function DateRangePicker(props: DateRangePickerProps) {
136
135
  </I18nProvider>
137
136
  )
138
137
  }
138
+
139
+ function ChevronLeft() {
140
+ return (
141
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4">
142
+ <path
143
+ fillRule="evenodd"
144
+ d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
145
+ clipRule="evenodd"
146
+ />
147
+ </svg>
148
+ )
149
+ }
150
+
151
+ function ChevronRight() {
152
+ return (
153
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4">
154
+ <path
155
+ fillRule="evenodd"
156
+ d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
157
+ clipRule="evenodd"
158
+ />
159
+ </svg>
160
+ )
161
+ }
@@ -92,7 +92,11 @@ function DisclosureHeader({
92
92
  />
93
93
  <span
94
94
  className={tw(
95
- size === 'lg' ? 'text-lg font-semibold' : size === 'md' ? 'text-base font-medium' : 'text-sm font-medium',
95
+ size === 'lg'
96
+ ? 'text-lg font-semibold'
97
+ : size === 'md'
98
+ ? 'text-base font-medium'
99
+ : 'text-sm font-medium',
96
100
  isDisabled ? 'text-neutral-400' : 'text-neutral-700'
97
101
  )}
98
102
  >
@@ -76,32 +76,34 @@ type FieldGroupProps = {
76
76
  onClick?: React.MouseEventHandler<HTMLDivElement>
77
77
  }
78
78
 
79
- const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(({ children, className, onClick, ...opts }, ref) => {
80
- const disabled = opts.isDisabled || opts.isReadonly
81
- const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'
79
+ const FieldGroup = React.forwardRef<HTMLDivElement, FieldGroupProps>(
80
+ ({ children, className, onClick, ...opts }, ref) => {
81
+ const disabled = opts.isDisabled || opts.isReadonly
82
+ const shape = opts.shape === 'oval' ? 'rounded-full' : 'rounded'
82
83
 
83
- return (
84
- <Group
85
- ref={ref}
86
- className={tw(
87
- 'relative overflow-hidden ring-1 shadow-sm transition-all duration-200 max-w-96',
88
- 'focus-within:outline-2 focus-within:-outline-offset-1',
89
- shape,
90
- disabled && 'ring-neutral-200 bg-neutral-50 text-neutral-400 shadow-none cursor-not-allowed',
91
- opts.isInvalid && 'ring-negative-300 bg-negative-50 focus-within:outline-negative-500',
92
- opts.hasWarning && 'ring-notice-300 bg-notice-50 focus-within:outline-notice-500',
93
- !opts.isInvalid &&
94
- !opts.hasWarning &&
95
- !disabled &&
96
- 'ring-black/10 hover:ring-black/20 focus-within:outline-primary-500',
97
- className
98
- )}
99
- onClick={onClick}
100
- >
101
- {children}
102
- </Group>
103
- )
104
- })
84
+ return (
85
+ <Group
86
+ ref={ref}
87
+ className={tw(
88
+ 'relative overflow-hidden ring-1 shadow-sm transition-all duration-200 max-w-96',
89
+ 'focus-within:outline-2 focus-within:-outline-offset-1',
90
+ shape,
91
+ disabled && 'ring-neutral-200 bg-neutral-50 text-neutral-400 shadow-none cursor-not-allowed',
92
+ opts.isInvalid && 'ring-negative-300 bg-negative-50 focus-within:outline-negative-500',
93
+ opts.hasWarning && 'ring-notice-300 bg-notice-50 focus-within:outline-notice-500',
94
+ !opts.isInvalid &&
95
+ !opts.hasWarning &&
96
+ !disabled &&
97
+ 'ring-black/10 hover:ring-black/20 focus-within:outline-primary-500',
98
+ className
99
+ )}
100
+ onClick={onClick}
101
+ >
102
+ {children}
103
+ </Group>
104
+ )
105
+ }
106
+ )
105
107
 
106
108
  const FieldInput = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
107
109
  return (
@@ -36,10 +36,7 @@ export type LinkTabProps = {
36
36
 
37
37
  // --- LinkTabList ---
38
38
 
39
- const LinkTabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({
40
- color: 'primary',
41
- variant: 'underline'
42
- })
39
+ const LinkTabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({ color: 'primary', variant: 'underline' })
43
40
 
44
41
  function LinkTabList({ children, className, color = 'primary', variant = 'underline' }: LinkTabListProps) {
45
42
  const containerRef = React.useRef<HTMLDivElement>(null)
@@ -51,7 +48,11 @@ function LinkTabList({ children, className, color = 'primary', variant = 'underl
51
48
  <div role="tablist" className={tw(tabListInnerClasses[variant], className)}>
52
49
  {children}
53
50
  </div>
54
- <div ref={indicatorRef} className={getIndicatorClasses(variant, color)} style={{ opacity: 0 }} />
51
+ <div
52
+ ref={indicatorRef}
53
+ className={getIndicatorClasses(variant, color)}
54
+ style={{ opacity: 0 }}
55
+ />
55
56
  </div>
56
57
  </LinkTabListContext.Provider>
57
58
  )
@@ -72,7 +73,13 @@ function LinkTab({ to, children, className, disabled, replace, end }: LinkTabPro
72
73
  <span
73
74
  role="tab"
74
75
  aria-disabled="true"
75
- className={tw(baseClasses, 'outline-none', TAB_INACTIVE_CLASSES, TAB_DISABLED_CLASSES, className)}
76
+ className={tw(
77
+ baseClasses,
78
+ 'outline-none',
79
+ TAB_INACTIVE_CLASSES,
80
+ TAB_DISABLED_CLASSES,
81
+ className
82
+ )}
76
83
  >
77
84
  {children}
78
85
  </span>
@@ -90,9 +97,7 @@ function LinkTab({ to, children, className, disabled, replace, end }: LinkTabPro
90
97
  baseClasses,
91
98
  focusClasses,
92
99
  isActive
93
- ? variant === 'oval'
94
- ? TAB_OVAL_SELECTED_CLASSES
95
- : `${colors.text} font-bold`
100
+ ? (variant === 'oval' ? TAB_OVAL_SELECTED_CLASSES : `${colors.text} font-bold`)
96
101
  : `${TAB_INACTIVE_CLASSES} hover:text-neutral-700`,
97
102
  className
98
103
  )
@@ -162,9 +162,7 @@ export function MultiFileInput(props: MultiFileInputProps) {
162
162
  <Icon name="document" className="h-5 w-5 text-neutral-400" />
163
163
  <div className="min-w-0">
164
164
  <p className="text-sm font-medium text-neutral-900 truncate">{fileName || 'File'}</p>
165
- {fileItem.file && (
166
- <p className="text-xs text-neutral-500">{formatFileSize(fileItem.file.size)}</p>
167
- )}
165
+ {fileItem.file && <p className="text-xs text-neutral-500">{formatFileSize(fileItem.file.size)}</p>}
168
166
  </div>
169
167
  </div>
170
168
  {!props.isDisabled && !props.isReadonly && (
@@ -180,13 +180,7 @@ export function NumericTagField(props: NumericTagFieldProps) {
180
180
 
181
181
  <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />
182
182
 
183
- <FormFieldComponents.FieldGroup
184
- isDisabled={props.isDisabled}
185
- isInvalid={isInvalid}
186
- hasWarning={hasWarning}
187
- shape={props.shape}
188
- onClick={() => inputRef.current?.focus()}
189
- >
183
+ <FormFieldComponents.FieldGroup isDisabled={props.isDisabled} isInvalid={isInvalid} hasWarning={hasWarning} shape={props.shape} onClick={() => inputRef.current?.focus()}>
190
184
  <div className="flex flex-wrap items-center gap-1.5 px-3 py-1.5 min-h-[38px] w-full">
191
185
  {/* Display existing tags */}
192
186
  {tags.map((tag, index) => (
@@ -28,10 +28,7 @@ type MyLocalization = TableLocalization<FC, SC>
28
28
  // String union → DefaultTableFilter (has options array with typed values, no `type` discriminant)
29
29
  const _selectFilter: TableFilterLocalization<StatusValue> = {
30
30
  label: 'Status',
31
- options: [
32
- { label: 'Active', value: 'active' },
33
- { label: 'Inactive', value: 'inactive' }
34
- ]
31
+ options: [{ label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }]
35
32
  }
36
33
 
37
34
  // Date range → DateRangeTableFilter (type: 'date-range', no value-typed options)
@@ -67,7 +67,10 @@ function testLocalizationWithSelectFilter() {
67
67
  }
68
68
  })
69
69
 
70
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
70
+ type Loc = TableLocalization<
71
+ ReturnType<typeof table.getFilterConfig>,
72
+ ReturnType<typeof table.getSortConfig>
73
+ >
71
74
 
72
75
  type _LocFilterKeys = Expect<Equal<keyof Loc['filters'], 'status'>>
73
76
  type _LocSortKeys = Expect<Equal<keyof Loc['sorts'], 'newest' | 'oldest'>>
@@ -108,7 +111,10 @@ function testLocalizationWithDateRangeFilter() {
108
111
  }
109
112
  })
110
113
 
111
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
114
+ type Loc = TableLocalization<
115
+ ReturnType<typeof table.getFilterConfig>,
116
+ ReturnType<typeof table.getSortConfig>
117
+ >
112
118
 
113
119
  // Valid: date-range filter accepts DateRangeTableFilter form
114
120
  const _loc: Loc = {
@@ -201,7 +207,10 @@ function testNoFiltersOrSorts() {
201
207
  getRowId: (row) => row.id
202
208
  })
203
209
 
204
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
210
+ type Loc = TableLocalization<
211
+ ReturnType<typeof table.getFilterConfig>,
212
+ ReturnType<typeof table.getSortConfig>
213
+ >
205
214
 
206
215
  type _EmptyFilters = Expect<Equal<Loc['filters'], {}>>
207
216
  type _EmptySorts = Expect<Equal<Loc['sorts'], {}>>
@@ -26,11 +26,7 @@ type Row = {
26
26
  // =============================================================================
27
27
 
28
28
  function testFilterAndSortInference() {
29
- const serverTable = null as unknown as ServerTable.ServerTable<
30
- Row,
31
- { status: 'active' | 'inactive' },
32
- { newest: true; oldest: true }
33
- >
29
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, { status: 'active' | 'inactive' }, { newest: true; oldest: true }>
34
30
 
35
31
  const table = useServerTable(serverTable, { getRowId: (row) => row.id })
36
32
 
@@ -52,15 +48,14 @@ function testFilterAndSortInference() {
52
48
  // =============================================================================
53
49
 
54
50
  function testLocalizationWithSelectFilter() {
55
- const serverTable = null as unknown as ServerTable.ServerTable<
56
- Row,
57
- { status: 'active' | 'inactive' },
58
- { newest: true; oldest: true }
59
- >
51
+ const serverTable = null as unknown as ServerTable.ServerTable<Row, { status: 'active' | 'inactive' }, { newest: true; oldest: true }>
60
52
 
61
53
  const table = useServerTable(serverTable, { getRowId: (row) => row.id })
62
54
 
63
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
55
+ type Loc = TableLocalization<
56
+ ReturnType<typeof table.getFilterConfig>,
57
+ ReturnType<typeof table.getSortConfig>
58
+ >
64
59
 
65
60
  type _LocFilterKeys = Expect<Equal<keyof Loc['filters'], 'status'>>
66
61
  type _LocSortKeys = Expect<Equal<keyof Loc['sorts'], 'newest' | 'oldest'>>
@@ -101,7 +96,10 @@ function testLocalizationWithDateRangeFilter() {
101
96
 
102
97
  const table = useServerTable(serverTable, { getRowId: (row) => row.id })
103
98
 
104
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
99
+ type Loc = TableLocalization<
100
+ ReturnType<typeof table.getFilterConfig>,
101
+ ReturnType<typeof table.getSortConfig>
102
+ >
105
103
 
106
104
  // Valid: date-range filter accepts DateRangeTableFilter form
107
105
  const _loc: Loc = {
@@ -153,7 +151,10 @@ function testNoFiltersOrSorts() {
153
151
 
154
152
  const table = useServerTable(serverTable, { getRowId: (row) => row.id })
155
153
 
156
- type Loc = TableLocalization<ReturnType<typeof table.getFilterConfig>, ReturnType<typeof table.getSortConfig>>
154
+ type Loc = TableLocalization<
155
+ ReturnType<typeof table.getFilterConfig>,
156
+ ReturnType<typeof table.getSortConfig>
157
+ >
157
158
 
158
159
  type _EmptyFilters = Expect<Equal<Loc['filters'], {}>>
159
160
  type _EmptySorts = Expect<Equal<Loc['sorts'], {}>>
@@ -69,10 +69,7 @@ TabGroup.displayName = 'TabGroup'
69
69
 
70
70
  // --- TabList ---
71
71
 
72
- const TabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({
73
- color: 'primary',
74
- variant: 'underline'
75
- })
72
+ const TabListContext = React.createContext<{ color: TabColor; variant: TabVariant }>({ color: 'primary', variant: 'underline' })
76
73
 
77
74
  function TabList({ children, className, color = 'primary', variant = 'underline' }: TabListProps) {
78
75
  const containerRef = React.useRef<HTMLDivElement>(null)
@@ -82,7 +79,11 @@ function TabList({ children, className, color = 'primary', variant = 'underline'
82
79
  <TabListContext.Provider value={{ color, variant }}>
83
80
  <div ref={containerRef} className={tabListContainerClasses[variant]}>
84
81
  <AriaTabList className={tw(tabListInnerClasses[variant], className)}>{children}</AriaTabList>
85
- <div ref={indicatorRef} className={getIndicatorClasses(variant, color)} style={{ opacity: 0 }} />
82
+ <div
83
+ ref={indicatorRef}
84
+ className={getIndicatorClasses(variant, color)}
85
+ style={{ opacity: 0 }}
86
+ />
86
87
  </div>
87
88
  </TabListContext.Provider>
88
89
  )
@@ -105,9 +106,7 @@ function TabItem({ children, className, ...props }: TabProps) {
105
106
  variant === 'oval' ? TAB_OVAL_BASE_CLASSES : TAB_BASE_CLASSES,
106
107
  focusClasses,
107
108
  isSelected
108
- ? variant === 'oval'
109
- ? TAB_OVAL_SELECTED_CLASSES
110
- : `${colors.text} font-bold`
109
+ ? (variant === 'oval' ? TAB_OVAL_SELECTED_CLASSES : `${colors.text} font-bold`)
111
110
  : `${TAB_INACTIVE_CLASSES} data-[hovered]:text-neutral-700`,
112
111
  isDisabled && TAB_DISABLED_CLASSES,
113
112
  className
@@ -150,13 +150,7 @@ export function TagField(props: TagFieldProps) {
150
150
 
151
151
  <FormFieldComponents.LabelRow label={props.label} cornerHint={props.cornerHint} htmlFor={inputId} />
152
152
 
153
- <FormFieldComponents.FieldGroup
154
- isDisabled={props.isDisabled}
155
- isInvalid={isInvalid}
156
- hasWarning={hasWarning}
157
- shape={props.shape}
158
- onClick={() => inputRef.current?.focus()}
159
- >
153
+ <FormFieldComponents.FieldGroup isDisabled={props.isDisabled} isInvalid={isInvalid} hasWarning={hasWarning} shape={props.shape} onClick={() => inputRef.current?.focus()}>
160
154
  <div className="flex flex-wrap items-center gap-1.5 px-3 py-1.5 min-h-[38px] w-full">
161
155
  {/* Display existing tags */}
162
156
  {tags.map((tag, index) =>
@@ -47,8 +47,6 @@ export type TextFieldProps = {
47
47
 
48
48
  export function TextField(props: TextFieldProps) {
49
49
  const inputRef = React.useRef<HTMLInputElement>(null)
50
- const visibleRef = React.useRef<HTMLInputElement>(null)
51
-
52
50
  const helpTextId = React.useId()
53
51
  const inputId = React.useId()
54
52
 
@@ -68,7 +66,7 @@ export function TextField(props: TextFieldProps) {
68
66
  isRequired: props.isRequired,
69
67
  form: props.form,
70
68
  focus() {
71
- visibleRef.current?.focus()
69
+ inputRef.current?.focus()
72
70
  }
73
71
  },
74
72
  inputRef
@@ -115,7 +113,6 @@ export function TextField(props: TextFieldProps) {
115
113
  inputMode={props.inputMode}
116
114
  aria-invalid={isInvalid || undefined}
117
115
  aria-describedby={helpTextId}
118
- ref={visibleRef}
119
116
  />
120
117
  {props.trailingInlineAddon}
121
118
  </FormFieldComponents.FieldGroup>
@@ -476,10 +476,7 @@ function getSearchParamsState<L extends QueryStateLoader>(
476
476
  * Reads from the pending navigation location when one is in flight so the UI
477
477
  * reflects optimistic state during transitions.
478
478
  */
479
- function useState<S>(
480
- name: string,
481
- codec: Codec<CodecDef<S>>
482
- ): [S, (val: S | ((prevState: S) => S), navigateOpts?: QueryStateNavigateOptions) => void] {
479
+ function useState<S>(name: string, codec: Codec<CodecDef<S>>): [S, (val: S | ((prevState: S) => S), navigateOpts?: QueryStateNavigateOptions) => void] {
483
480
  const [searchParamsRaw, setSearchParams] = useSearchParamsRR()
484
481
  const navigation = useNavigation()
485
482
 
@@ -46,8 +46,7 @@ export function getIndicatorClasses(variant: TabVariant, color: TabColor): strin
46
46
  return 'absolute inset-y-1 rounded-md bg-white shadow-sm'
47
47
  }
48
48
 
49
- export const TAB_OVAL_BASE_CLASSES =
50
- 'relative z-10 px-4 py-1.5 text-sm cursor-default transition-colors duration-200 rounded-md'
49
+ export const TAB_OVAL_BASE_CLASSES = 'relative z-10 px-4 py-1.5 text-sm cursor-default transition-colors duration-200 rounded-md'
51
50
  export const TAB_OVAL_SELECTED_CLASSES = 'text-neutral-900 font-semibold'
52
51
 
53
52
  // --- Animated Indicator Hook ---