@maestro-js/components 1.0.0-alpha.18 → 1.0.0-alpha.2

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 (56) hide show
  1. package/commands/add.ts +1 -16
  2. package/dist/components.json +1 -1
  3. package/dist/index.js +1 -14
  4. package/package.json +5 -5
  5. package/registry.json +790 -598
  6. package/scripts/build.ts +4 -11
  7. package/src/components/alert-dialog.tsx +3 -3
  8. package/src/components/avatar.tsx +1 -1
  9. package/src/components/button-link.tsx +0 -3
  10. package/src/components/button.tsx +0 -4
  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/dialog.tsx +7 -21
  15. package/src/components/disclosure.tsx +1 -1
  16. package/src/components/drawer.tsx +5 -19
  17. package/src/components/form.tsx +5 -27
  18. package/src/components/helpers/form-field.tsx +8 -7
  19. package/src/components/helpers/get-button-classes.ts +23 -23
  20. package/src/components/helpers/headless-button.tsx +1 -1
  21. package/src/components/inline-alert.tsx +3 -3
  22. package/src/components/labeled-value.tsx +2 -2
  23. package/src/components/menu.tsx +26 -60
  24. package/src/components/month-day-input.tsx +1 -2
  25. package/src/components/multiselect.tsx +70 -74
  26. package/src/components/radio.tsx +8 -8
  27. package/src/components/select.tsx +8 -9
  28. package/src/components/stepper.tsx +6 -6
  29. package/src/components/switch.tsx +7 -7
  30. package/src/components/tabs.tsx +1 -1
  31. package/src/components/tag-field.tsx +3 -3
  32. package/src/components/text-area.tsx +1 -1
  33. package/src/components/text-field.tsx +1 -4
  34. package/src/components/toast.tsx +6 -6
  35. package/src/components/toggle-button-group.tsx +2 -2
  36. package/src/utils/icons.d.ts +1 -2
  37. package/src/utils/use-tab-indicator.ts +9 -9
  38. package/src/components/date-range.tsx +0 -138
  39. package/src/components/optional-link.tsx +0 -15
  40. package/src/components/table/headless-templated-row-table.ts +0 -550
  41. package/src/components/table/index.tsx +0 -41
  42. package/src/components/table/server-table.ts +0 -143
  43. package/src/components/table/table-actions.tsx +0 -76
  44. package/src/components/table/table-container.tsx +0 -173
  45. package/src/components/table/table-container.type-test.ts +0 -155
  46. package/src/components/table/table-filters/date-range-filter.tsx +0 -227
  47. package/src/components/table/table-filters/select-filter.tsx +0 -211
  48. package/src/components/table/table-filters/sort.tsx +0 -85
  49. package/src/components/table/table-primitives.tsx +0 -226
  50. package/src/components/table/table-toolbar.tsx +0 -300
  51. package/src/components/table/table-types.ts +0 -61
  52. package/src/components/table/use-client-table.ts +0 -157
  53. package/src/components/table/use-client-table.type-test.ts +0 -217
  54. package/src/components/table/use-server-table.ts +0 -192
  55. package/src/components/table/use-server-table.type-test.ts +0 -174
  56. package/src/utils/use-query-state.ts +0 -626
@@ -7,7 +7,6 @@ import {
7
7
  Pressable,
8
8
  Separator as AriaSeparator
9
9
  } from 'react-aria-components'
10
- import { Link, useHref, type RelativeRoutingType, type To } from 'react-router'
11
10
  import { AnimatedPopover } from './helpers/animated-popover'
12
11
  import { tw } from '../utils/tw'
13
12
 
@@ -19,26 +18,20 @@ export type MenuProps = {
19
18
  children?: React.ReactNode
20
19
  className?: string
21
20
  containerClassName?: string
22
- placement?: 'bottom' | 'bottom start' | 'bottom end' | 'top' | 'top start' | 'top end' | 'left' | 'right'
21
+ placement?: 'bottom' | 'bottom start' | 'bottom end' | 'top' | 'top start' | 'top end'
23
22
  }
24
23
 
25
24
  export type MenuItemProps = {
26
25
  children?: React.ReactNode
27
26
  className?: string
28
27
  isDisabled?: boolean
28
+ onAction?: () => void
29
29
  id?: string
30
30
  textValue?: string
31
- type?: 'button' | 'submit'
32
- onAction?: () => void
33
- onClick?: ((e: React.MouseEvent<Element & HTMLOrSVGElement, MouseEvent>) => void) | undefined
34
- form?: string
35
- name?: string
36
- value?: string
37
31
  }
38
32
 
39
33
  export type MenuLinkItemProps = {
40
- to: To
41
- relative?: RelativeRoutingType
34
+ href: string
42
35
  children?: React.ReactNode
43
36
  className?: string
44
37
  isDisabled?: boolean
@@ -52,17 +45,6 @@ export type MenuGroupProps = {
52
45
  children?: React.ReactNode
53
46
  }
54
47
 
55
- // ── Helpers ──────────────────────────────────────────────────────────────────
56
-
57
- function itemClassName({ isFocused, isDisabled }: { isFocused: boolean; isDisabled: boolean }, className?: string) {
58
- return tw(
59
- isFocused ? 'bg-neutral-50 text-neutral-900' : 'text-neutral-700',
60
- 'group flex w-full items-center px-4 py-2 text-sm gap-2 outline-none',
61
- isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
62
- className
63
- )
64
- }
65
-
66
48
  // ── MenuMain ──────────────────────────────────────────────────────────────────
67
49
 
68
50
  function MenuMain({ Button: ButtonProp, children, className, containerClassName, placement = 'bottom end' }: MenuProps) {
@@ -85,7 +67,7 @@ function MenuMain({ Button: ButtonProp, children, className, containerClassName,
85
67
  <AnimatedPopover placement={placement} offset={8}>
86
68
  <AriaMenu
87
69
  className={tw(
88
- 'overflow-hidden min-w-56 w-max rounded-lg bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none',
70
+ 'overflow-hidden min-w-56 w-max rounded bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none',
89
71
  className
90
72
  )}
91
73
  autoFocus="first"
@@ -102,62 +84,46 @@ MenuMain.displayName = 'Menu'
102
84
 
103
85
  // ── MenuItem ──────────────────────────────────────────────────────────────────
104
86
 
105
- function Item({
106
- children,
107
- className,
108
- isDisabled,
109
- id,
110
- textValue,
111
- type,
112
- onAction,
113
- onClick,
114
- form,
115
- name,
116
- value
117
- }: MenuItemProps) {
118
- const itemRef = React.useRef<HTMLDivElement>(null)
119
- const isSubmit = type === 'submit'
120
-
87
+ function Item({ children, className, isDisabled, onAction, id, textValue }: MenuItemProps) {
121
88
  return (
122
89
  <AriaMenuItem
123
90
  id={id}
124
- ref={isSubmit ? itemRef : undefined}
125
- onClick={onClick}
126
91
  textValue={textValue ?? (typeof children === 'string' ? children : undefined)}
127
- onAction={
128
- isSubmit
129
- ? () => {
130
- onAction?.()
131
- const targetForm = form
132
- ? (document.getElementById(form) as HTMLFormElement | null)
133
- : (itemRef.current?.closest('form') ?? null)
134
- if (!targetForm) return
135
- targetForm.requestSubmit()
136
- }
137
- : onAction
138
- }
92
+ onAction={onAction}
139
93
  isDisabled={isDisabled}
140
- className={(renderProps) => itemClassName(renderProps, className)}
94
+ className={({ isFocused, isDisabled }) =>
95
+ tw(
96
+ isFocused ? 'bg-neutral-100 text-neutral-900' : 'text-neutral-700',
97
+ 'group flex w-full items-center px-4 py-2 text-sm outline-none',
98
+ isDisabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : 'cursor-pointer',
99
+ className
100
+ )
101
+ }
141
102
  >
142
103
  {children}
143
- {isSubmit && <input type="submit" className="sr-only" name={name} value={value ?? ''} form={form} />}
144
104
  </AriaMenuItem>
145
105
  )
146
106
  }
147
107
 
148
108
  // ── LinkItem ──────────────────────────────────────────────────────────────────
149
109
 
150
- function LinkItem({ to, children, className, relative, isDisabled, target, rel, id, textValue }: MenuLinkItemProps) {
110
+ function LinkItem({ href, children, className, isDisabled, target, rel, id, textValue }: MenuLinkItemProps) {
151
111
  return (
152
112
  <AriaMenuItem
153
113
  id={id}
154
- href={useHref(to, { relative })}
114
+ textValue={textValue ?? (typeof children === 'string' ? children : undefined)}
115
+ href={href}
155
116
  target={target}
156
117
  rel={rel}
157
- textValue={textValue ?? (typeof children === 'string' ? children : undefined)}
158
118
  isDisabled={isDisabled}
159
- className={(renderProps) => itemClassName(renderProps, className)}
160
- render={(props) => <Link {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)} relative={relative} to={to} />}
119
+ className={({ isFocused, isDisabled }) =>
120
+ tw(
121
+ isFocused ? 'bg-neutral-100 text-neutral-900' : 'text-neutral-700',
122
+ 'group flex items-center px-4 py-2 text-sm gap-2 outline-none',
123
+ isDisabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : 'cursor-pointer',
124
+ className
125
+ )
126
+ }
161
127
  >
162
128
  {children}
163
129
  </AriaMenuItem>
@@ -173,7 +139,7 @@ function Group({ children }: MenuGroupProps) {
173
139
  // ── Separator ─────────────────────────────────────────────────────────────────
174
140
 
175
141
  function MenuSeparator() {
176
- return <AriaSeparator className="border-t border-neutral-950/5" />
142
+ return <AriaSeparator className="border-t border-neutral-100" />
177
143
  }
178
144
 
179
145
  // ── Export ─────────────────────────────────────────────────────────────────────
@@ -165,8 +165,7 @@ export function MonthDayInput(props: MonthDayInputProps) {
165
165
  function parseMonthDay(monthDay: Iso.MonthDay): CalendarDate | null {
166
166
  if (!monthDayFns.isValid(monthDay)) return null
167
167
  // Use a leap-year placeholder so Feb 29 is valid
168
- const { month, day } = monthDayFns.getFields(monthDay)
169
- return new CalendarDate(1972, month, day)
168
+ return new CalendarDate(1972, monthDayFns.getMonth(monthDay), monthDayFns.getDay(monthDay))
170
169
  }
171
170
 
172
171
  function formatMonthDay(date: CalendarDate | null): Iso.MonthDay | null {
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import { ListBox, ListBoxItem } from 'react-aria-components'
3
+ import { AnimatedPopover } from './helpers/animated-popover'
3
4
  import type { Selection } from 'react-aria-components'
4
5
  import { HeadlessForm } from '@maestro-js/form'
5
6
  import type { ValidationFunction, ValidationResult } from '@maestro-js/form'
@@ -57,7 +58,6 @@ export type MultiselectProps<T extends MultiselectOption = MultiselectOption> =
57
58
  export function Multiselect<T extends MultiselectOption = MultiselectOption>(props: MultiselectProps<T>) {
58
59
  const hiddenInputRef = React.useRef<HTMLInputElement>(null)
59
60
  const buttonRef = React.useRef<HTMLButtonElement>(null)
60
- const popoverContentRef = React.useRef<HTMLDivElement>(null)
61
61
  const [containerRef, setContainerRef] = React.useState<HTMLButtonElement | null>(null)
62
62
  const helpTextId = React.useId()
63
63
  const labelId = React.useId()
@@ -89,19 +89,6 @@ export function Multiselect<T extends MultiselectOption = MultiselectOption>(pro
89
89
  hiddenInputRef
90
90
  )
91
91
 
92
- // Close dropdown on outside click
93
- React.useEffect(() => {
94
- if (!isOpen) return
95
- const handlePointerDown = (e: PointerEvent) => {
96
- const target = e.target as Node
97
- if (popoverContentRef.current?.contains(target) || buttonRef.current?.contains(target)) return
98
- setIsOpen(false)
99
- commitValidation()
100
- }
101
- document.addEventListener('pointerdown', handlePointerDown)
102
- return () => document.removeEventListener('pointerdown', handlePointerDown)
103
- })
104
-
105
92
  const isInvalid = props.isInvalid || validationInvalid
106
93
  const hasWarning = Boolean(props.warningMessage) && !isInvalid
107
94
 
@@ -248,73 +235,82 @@ export function Multiselect<T extends MultiselectOption = MultiselectOption>(pro
248
235
  </button>
249
236
 
250
237
  {/* Options dropdown */}
251
- {isOpen && !props.isDisabled && (
252
- <div
253
- ref={popoverContentRef}
254
- className={tw(
255
- 'absolute z-50 mt-1 overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none'
256
- )}
257
- style={{ width: containerSize.width, minWidth: containerSize.width }}
258
- >
259
- {props.showSelectAll && (
260
- <button
261
- type="button"
262
- onClick={handleSelectAll}
263
- className="w-full px-3 py-2 text-left hover:bg-neutral-50 border-b border-neutral-100 text-sm font-medium"
264
- >
265
- {selectedValues.length === props.options.filter((o) => !o.isDisabled).length ? 'Deselect All' : 'Select All'}
266
- </button>
267
- )}
268
- <ListBox
269
- aria-label={props.label ? String(props.label) : 'Multiselect'}
270
- selectionMode="multiple"
271
- selectedKeys={selectedKeys}
272
- onSelectionChange={handleSelectionChange}
273
- className="max-h-60 overflow-y-auto focus:outline-none"
274
- items={props.options.map((option, index) => ({ ...option, index, id: String(option.value) }))}
275
- renderEmptyState={() => <div className="px-3 py-2 text-neutral-500 text-sm">No options available</div>}
238
+ <AnimatedPopover
239
+ triggerRef={buttonRef}
240
+ isNonModal
241
+ isOpen={isOpen && !props.isDisabled}
242
+ onOpenChange={(open) => {
243
+ setIsOpen(open)
244
+ if (!open) commitValidation()
245
+ }}
246
+ placement="bottom start"
247
+ offset={4}
248
+ style={{ width: containerSize.width, minWidth: containerSize.width }}
249
+ >
250
+ <div
251
+ className={tw(
252
+ 'overflow-hidden rounded bg-white border border-neutral-200 shadow-lg text-sm w-full focus:outline-none'
253
+ )}
276
254
  >
277
- {(item) => {
278
- const option = item as T & { index: number; id: string }
279
- const color = props.colored ? getColor(stringToNumber(String(option.value))) : null
255
+ {props.showSelectAll && (
256
+ <button
257
+ type="button"
258
+ onClick={handleSelectAll}
259
+ className="w-full px-3 py-2 text-left hover:bg-neutral-50 border-b border-neutral-100 text-sm font-medium"
260
+ >
261
+ {selectedValues.length === props.options.filter((o) => !o.isDisabled).length ? 'Deselect All' : 'Select All'}
262
+ </button>
263
+ )}
264
+ <ListBox
265
+ aria-label={props.label ? String(props.label) : 'Multiselect'}
266
+ selectionMode="multiple"
267
+ selectedKeys={selectedKeys}
268
+ onSelectionChange={handleSelectionChange}
269
+ className="max-h-60 overflow-y-auto focus:outline-none"
270
+ items={props.options.map((option, index) => ({ ...option, index, id: String(option.value) }))}
271
+ renderEmptyState={() => <div className="px-3 py-2 text-neutral-500 text-sm">No options available</div>}
272
+ >
273
+ {(item) => {
274
+ const option = item as T & { index: number; id: string }
275
+ const color = props.colored ? getColor(stringToNumber(String(option.value))) : null
276
+
277
+ if (props.renderOption) {
278
+ return (
279
+ <ListBoxItem
280
+ id={option.id}
281
+ textValue={option.label}
282
+ isDisabled={option.isDisabled}
283
+ className="outline-none"
284
+ >
285
+ {({ isSelected }) => (
286
+ <>
287
+ {props.renderOption!({
288
+ option,
289
+ selected: isSelected,
290
+ colored: props.colored ?? false,
291
+ index: option.index
292
+ })}
293
+ </>
294
+ )}
295
+ </ListBoxItem>
296
+ )
297
+ }
280
298
 
281
- if (props.renderOption) {
282
299
  return (
283
- <ListBoxItem
300
+ <FormFieldComponents.DropdownItem
284
301
  id={option.id}
285
302
  textValue={option.label}
303
+ label={option.label}
304
+ secondary={option.secondary}
286
305
  isDisabled={option.isDisabled}
287
- className="outline-none"
288
- >
289
- {({ isSelected }) => (
290
- <>
291
- {props.renderOption!({
292
- option,
293
- selected: isSelected,
294
- colored: props.colored ?? false,
295
- index: option.index
296
- })}
297
- </>
298
- )}
299
- </ListBoxItem>
306
+ color={color}
307
+ className="px-3"
308
+ />
300
309
  )
301
- }
302
-
303
- return (
304
- <FormFieldComponents.DropdownItem
305
- id={option.id}
306
- textValue={option.label}
307
- label={option.label}
308
- secondary={option.secondary}
309
- isDisabled={option.isDisabled}
310
- color={color}
311
- className="px-3"
312
- />
313
- )
314
- }}
315
- </ListBox>
316
- </div>
317
- )}
310
+ }}
311
+ </ListBox>
312
+ </div>
313
+ </AnimatedPopover>
318
314
 
319
315
  <FormFieldComponents.FormFieldHelpText
320
316
  id={helpTextId}
@@ -25,7 +25,7 @@ export function PlainRadio(props: PlainRadioProps) {
25
25
  value={props.value}
26
26
  isDisabled={props.isDisabled}
27
27
  aria-label={props['aria-label']}
28
- className="group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
28
+ className="group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2"
29
29
  >
30
30
  {({ isSelected, isDisabled }) => <RadioCircle isSelected={isSelected} isDisabled={isDisabled} />}
31
31
  </AriaRadio>
@@ -41,11 +41,11 @@ export function LabeledRadio(props: LabeledRadioProps) {
41
41
  <AriaRadio
42
42
  value={props.value}
43
43
  isDisabled={props.isDisabled}
44
- className="group flex items-start gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
44
+ className="group flex items-start gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2"
45
45
  >
46
46
  {({ isSelected, isDisabled }) => (
47
47
  <>
48
- <div className="shrink-0">
48
+ <div className="flex-shrink-0">
49
49
  <RadioCircle isSelected={isSelected} isDisabled={isDisabled} />
50
50
  </div>
51
51
  {props.label && (
@@ -74,16 +74,16 @@ function RadioCircle({ isSelected, isDisabled }: { isSelected: boolean; isDisabl
74
74
  return (
75
75
  <div
76
76
  className={tw(
77
- 'size-5 rounded-full border-2 transition-colors duration-200 flex items-center justify-center',
78
- isSelected ? 'border-primary-600 bg-primary-600' : 'border-neutral-300 bg-white',
77
+ 'h-5 w-5 rounded-full border-2 transition-all duration-50 flex items-center justify-center',
78
+ isSelected ? 'border-primary-500 bg-primary-500' : 'border-neutral-300 bg-white',
79
79
  !isDisabled && !isSelected && 'group-hovered:border-neutral-400',
80
- isDisabled && 'cursor-not-allowed border-neutral-300 bg-neutral-100'
80
+ isDisabled && 'opacity-50 cursor-not-allowed'
81
81
  )}
82
82
  >
83
83
  <div
84
84
  className={tw(
85
- 'size-2 rounded-full bg-white transition-transform duration-200',
86
- isSelected ? 'scale-100' : 'scale-0'
85
+ 'h-2 w-2 rounded-full bg-white transition-all duration-50',
86
+ isSelected ? 'scale-100 opacity-100' : 'scale-0 opacity-0'
87
87
  )}
88
88
  />
89
89
  </div>
@@ -114,19 +114,18 @@ export function Select<T extends SelectOption = SelectOption>(props: SelectProps
114
114
  <Button
115
115
  ref={buttonRef}
116
116
  className={tw(
117
- 'relative overflow-hidden ring-1 shadow-sm transition-all duration-200 max-w-96',
117
+ 'relative overflow-hidden border transition-all duration-200 max-w-96',
118
118
  props.shape === 'oval' ? 'rounded-full' : 'rounded',
119
- 'focus:outline-2 focus:-outline-offset-1',
120
- (props.isDisabled || props.isReadonly) &&
121
- 'ring-neutral-200 bg-neutral-50 text-neutral-400 shadow-none cursor-not-allowed',
122
- isInvalid && 'ring-negative-300 bg-negative-50 focus:outline-negative-500',
123
- hasWarning && 'ring-notice-300 bg-notice-50 focus:outline-notice-500',
119
+ 'focus:outline-none focus:ring-1',
120
+ (props.isDisabled || props.isReadonly) && 'border-neutral-100 bg-neutral-50 text-neutral-400 cursor-not-allowed',
121
+ isInvalid && 'border-negative-200 bg-negative-50 focus:border-negative-500 focus:ring-negative-500/20',
122
+ hasWarning && 'border-notice-200 bg-notice-50 focus:border-notice-500 focus:ring-notice-500/20',
124
123
  !isInvalid &&
125
124
  !hasWarning &&
126
125
  !props.isDisabled &&
127
126
  !props.isReadonly &&
128
- 'ring-black/10 hover:ring-black/20 focus:outline-primary-500',
129
- 'w-full cursor-default py-2 pl-3 pr-10 text-left text-sm max-sm:text-base placeholder:text-neutral-400'
127
+ 'border-neutral-200 hover:border-neutral-300 focus:border-primary-500 focus:ring-primary-500/20',
128
+ 'w-full cursor-default py-2 pl-3 pr-10 text-left placeholder:text-neutral-400'
130
129
  )}
131
130
  aria-invalid={isInvalid || undefined}
132
131
  aria-describedby={helpTextId}
@@ -140,7 +139,7 @@ export function Select<T extends SelectOption = SelectOption>(props: SelectProps
140
139
  {selectedOption ? (
141
140
  <span className="block truncate text-sm min-h-5">{selectedOption.label}</span>
142
141
  ) : (
143
- <span className="block truncate text-sm min-h-5 text-neutral-400 italic">
142
+ <span className="block truncate text-sm min-h-5 text-neutral-300 italic">
144
143
  {props.placeholder ?? 'Select an option'}
145
144
  </span>
146
145
  )}
@@ -100,12 +100,12 @@ function Step({
100
100
  <div className={tw('flex flex-row items-start flex-1 relative', className)}>
101
101
  {index !== 0 ? (
102
102
  <div className="flex flex-1 items-center pt-4 w-1/2 absolute left-0">
103
- <div className={tw('h-0.5 w-full transition-colors', firstConnectorColor)} />
103
+ <div className={tw('h-[2px] w-full transition-colors', firstConnectorColor)} />
104
104
  </div>
105
105
  ) : null}
106
106
  {!isLast ? (
107
107
  <div className="flex flex-1 items-center pt-4 w-1/2 absolute right-0">
108
- <div className={tw('h-0.5 w-full transition-colors', lastConnectorColor)} />
108
+ <div className={tw('h-[2px] w-full transition-colors', lastConnectorColor)} />
109
109
  </div>
110
110
  ) : null}
111
111
  <div className="flex flex-col items-center w-full relative">{children}</div>
@@ -119,12 +119,12 @@ function Step({
119
119
  <div className={tw('relative flex flex-col min-h-10', className)}>
120
120
  {index !== 0 ? (
121
121
  <div className="flex flex-1 justify-center pl-4 h-1/2 absolute top-0">
122
- <div className={tw('w-0.5 h-full transition-colors', firstConnectorColor)} />
122
+ <div className={tw('w-[2px] h-full transition-colors', firstConnectorColor)} />
123
123
  </div>
124
124
  ) : null}
125
125
  {!isLast ? (
126
126
  <div className="flex flex-1 items-center pl-4 h-1/2 absolute bottom-0">
127
- <div className={tw('w-0.5 h-full transition-colors', lastConnectorColor)} />
127
+ <div className={tw('w-[2px] h-full transition-colors', lastConnectorColor)} />
128
128
  </div>
129
129
  ) : null}
130
130
  <div className="flex flex-row w-full relative">{children}</div>
@@ -154,11 +154,11 @@ function StepLabel({ children, className }: StepLabelProps) {
154
154
  return (
155
155
  <div className={tw('flex items-center', orientation === 'horizontal' ? 'flex-col text-center' : 'gap-3', className)}>
156
156
  <div
157
- className={tw('flex size-8 shrink-0 items-center justify-center rounded-full border-2 transition-colors', iconBg)}
157
+ className={tw('flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 transition-colors', iconBg)}
158
158
  aria-current={active ? 'step' : undefined}
159
159
  >
160
160
  {completed ? (
161
- <Icon name="check" className="size-4" aria-hidden="true" />
161
+ <Icon name="check" className="h-4 w-4" aria-hidden="true" />
162
162
  ) : (
163
163
  <span className="text-sm font-semibold">{index + 1}</span>
164
164
  )}
@@ -56,7 +56,7 @@ export function PlainSwitch(props: PlainSwitchProps) {
56
56
  onChange={setValue}
57
57
  isDisabled={props.isDisabled}
58
58
  aria-label={props['aria-label']}
59
- className="group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
59
+ className="group inline-flex items-center cursor-pointer data-[disabled]:cursor-not-allowed focus:outline-none [&>div]:focus-visible:ring-2 [&>div]:focus-visible:ring-primary-500/20 [&>div]:focus-visible:ring-offset-2 [&>div]:focus-visible:rounded-full"
60
60
  >
61
61
  {({ isSelected, isDisabled }) => (
62
62
  <>
@@ -128,7 +128,7 @@ export function LabeledSwitch(props: LabeledSwitchProps) {
128
128
  }}
129
129
  isDisabled={props.isDisabled}
130
130
  aria-describedby={helpTextId}
131
- className="group flex items-center gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
131
+ className="group flex items-center gap-3 cursor-pointer data-[disabled]:cursor-not-allowed select-none focus:outline-none [&>div:first-child>div]:focus-visible:ring-2 [&>div:first-child>div]:focus-visible:ring-primary-500/20 [&>div:first-child>div]:focus-visible:ring-offset-2 [&>div:first-child>div]:focus-visible:rounded-full"
132
132
  >
133
133
  {({ isSelected, isDisabled }) => (
134
134
  <>
@@ -141,7 +141,7 @@ export function LabeledSwitch(props: LabeledSwitchProps) {
141
141
  />
142
142
  )}
143
143
  {labelPosition === 'left' && labelElement}
144
- <div className="shrink-0 flex items-center">
144
+ <div className="flex-shrink-0 flex items-center">
145
145
  <SwitchTrack isSelected={isSelected} isDisabled={isDisabled} />
146
146
  </div>
147
147
  {labelPosition === 'right' && labelElement}
@@ -170,14 +170,14 @@ function SwitchTrack({ isSelected, isDisabled }: { isSelected: boolean; isDisabl
170
170
  return (
171
171
  <div
172
172
  className={tw(
173
- 'relative inline-flex h-6 w-11 shrink-0 rounded-full border-2 border-transparent transition-colors duration-200',
174
- isSelected ? 'bg-primary-600' : 'bg-neutral-200',
175
- isDisabled && 'cursor-not-allowed bg-neutral-300'
173
+ 'relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out',
174
+ isSelected ? 'bg-primary-500' : 'bg-neutral-200',
175
+ isDisabled && 'opacity-50 cursor-not-allowed'
176
176
  )}
177
177
  >
178
178
  <span
179
179
  className={tw(
180
- 'pointer-events-none inline-block size-5 rounded-full bg-white shadow-sm ring-1 ring-black/5 transition-transform duration-200',
180
+ 'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
181
181
  isSelected ? 'translate-x-5' : 'translate-x-0'
182
182
  )}
183
183
  />
@@ -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 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:${colors.outline}`
99
+ const focusClasses = `outline-none data-[focus-visible]:outline-2 data-[focus-visible]:outline-offset-2 data-[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 pl-2 pr-1.5 py-0.5 text-xs font-normal bg-primary-500/10 text-primary-500',
229
+ 'inline-flex items-center rounded-full px-2 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 size-4 -m-0.5 p-0.5 rounded-full translate-x-1 hover:bg-neutral-950/10"
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"
242
242
  aria-label="Remove"
243
243
  tabIndex={-1}
244
244
  >
245
- <Icon name="x-mark" className="size-2" aria-hidden="true" />
245
+ <Icon name="x-mark" className="h-2 w-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 max-sm:text-base 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 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',
@@ -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>
@@ -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-lg px-4 py-3 shadow-lg',
102
+ 'flex gap-3 rounded-md border 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-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'
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'
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-primary-600',
85
+ !isSelected && 'text-neutral-700 data-[focus-visible]:outline-neutral-400',
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-600',
90
+ isSelected && 'data-[focus-visible]:outline-primary-500',
91
91
 
92
92
  'inline-flex justify-center items-center gap-x-2'
93
93
  )
@@ -13,8 +13,7 @@ declare module '~/icons/types' {
13
13
  'chevron-down',
14
14
  'exclamation-triangle',
15
15
  'minus',
16
- 'arrow-path',
17
- 'magnifying-glass'
16
+ 'arrow-path'
18
17
  ] as const
19
18
 
20
19
  export type IconName = (typeof iconNames)[number]