@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.
- package/commands/add.ts +1 -16
- package/dist/components.json +1 -1
- package/dist/index.js +1 -14
- package/package.json +5 -5
- package/registry.json +790 -598
- package/scripts/build.ts +4 -11
- package/src/components/alert-dialog.tsx +3 -3
- package/src/components/avatar.tsx +1 -1
- package/src/components/button-link.tsx +0 -3
- package/src/components/button.tsx +0 -4
- package/src/components/checkbox.tsx +7 -7
- package/src/components/chip.tsx +7 -7
- package/src/components/container.tsx +3 -3
- package/src/components/dialog.tsx +7 -21
- package/src/components/disclosure.tsx +1 -1
- package/src/components/drawer.tsx +5 -19
- package/src/components/form.tsx +5 -27
- package/src/components/helpers/form-field.tsx +8 -7
- package/src/components/helpers/get-button-classes.ts +23 -23
- package/src/components/helpers/headless-button.tsx +1 -1
- package/src/components/inline-alert.tsx +3 -3
- package/src/components/labeled-value.tsx +2 -2
- package/src/components/menu.tsx +26 -60
- package/src/components/month-day-input.tsx +1 -2
- package/src/components/multiselect.tsx +70 -74
- package/src/components/radio.tsx +8 -8
- package/src/components/select.tsx +8 -9
- package/src/components/stepper.tsx +6 -6
- package/src/components/switch.tsx +7 -7
- package/src/components/tabs.tsx +1 -1
- package/src/components/tag-field.tsx +3 -3
- package/src/components/text-area.tsx +1 -1
- package/src/components/text-field.tsx +1 -4
- package/src/components/toast.tsx +6 -6
- package/src/components/toggle-button-group.tsx +2 -2
- package/src/utils/icons.d.ts +1 -2
- package/src/utils/use-tab-indicator.ts +9 -9
- package/src/components/date-range.tsx +0 -138
- package/src/components/optional-link.tsx +0 -15
- package/src/components/table/headless-templated-row-table.ts +0 -550
- package/src/components/table/index.tsx +0 -41
- package/src/components/table/server-table.ts +0 -143
- package/src/components/table/table-actions.tsx +0 -76
- package/src/components/table/table-container.tsx +0 -173
- package/src/components/table/table-container.type-test.ts +0 -155
- package/src/components/table/table-filters/date-range-filter.tsx +0 -227
- package/src/components/table/table-filters/select-filter.tsx +0 -211
- package/src/components/table/table-filters/sort.tsx +0 -85
- package/src/components/table/table-primitives.tsx +0 -226
- package/src/components/table/table-toolbar.tsx +0 -300
- package/src/components/table/table-types.ts +0 -61
- package/src/components/table/use-client-table.ts +0 -157
- package/src/components/table/use-client-table.type-test.ts +0 -217
- package/src/components/table/use-server-table.ts +0 -192
- package/src/components/table/use-server-table.type-test.ts +0 -174
- package/src/utils/use-query-state.ts +0 -626
package/src/components/menu.tsx
CHANGED
|
@@ -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'
|
|
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
|
-
|
|
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
|
|
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={(
|
|
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({
|
|
110
|
+
function LinkItem({ href, children, className, isDisabled, target, rel, id, textValue }: MenuLinkItemProps) {
|
|
151
111
|
return (
|
|
152
112
|
<AriaMenuItem
|
|
153
113
|
id={id}
|
|
154
|
-
|
|
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={(
|
|
160
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
{
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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}
|
package/src/components/radio.tsx
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
'
|
|
78
|
-
isSelected ? 'border-primary-
|
|
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
|
|
80
|
+
isDisabled && 'opacity-50 cursor-not-allowed'
|
|
81
81
|
)}
|
|
82
82
|
>
|
|
83
83
|
<div
|
|
84
84
|
className={tw(
|
|
85
|
-
'
|
|
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
|
|
117
|
+
'relative overflow-hidden border transition-all duration-200 max-w-96',
|
|
118
118
|
props.shape === 'oval' ? 'rounded-full' : 'rounded',
|
|
119
|
-
'focus:outline-
|
|
120
|
-
(props.isDisabled || props.isReadonly) &&
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
'
|
|
129
|
-
'w-full cursor-default py-2 pl-3 pr-10 text-left
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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="
|
|
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:
|
|
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:
|
|
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-
|
|
175
|
-
isDisabled && 'cursor-not-allowed
|
|
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
|
|
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
|
/>
|
package/src/components/tabs.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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="
|
|
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
|
|
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
|
-
|
|
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>
|
package/src/components/toast.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
156
|
-
positive: 'bg-positive-50 text-positive-800
|
|
157
|
-
warning: 'bg-notice-50 text-notice-800
|
|
158
|
-
negative: 'bg-negative-50 text-negative-800
|
|
159
|
-
notice: 'bg-neutral-
|
|
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-
|
|
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-
|
|
90
|
+
isSelected && 'data-[focus-visible]:outline-primary-500',
|
|
91
91
|
|
|
92
92
|
'inline-flex justify-center items-center gap-x-2'
|
|
93
93
|
)
|