@podoba/react 0.0.1 → 0.0.4
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 +24 -15
- package/src/components/combobox.tsx +96 -0
- package/src/components/dialog.tsx +4 -0
- package/src/components/input.tsx +7 -4
- package/src/components/multiselect.tsx +146 -0
- package/src/components/select.tsx +9 -11
- package/src/components/textarea.tsx +8 -6
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podoba/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "podoba React components — React Aria Components + Tailwind primitives + layout, built with uic.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/graphic-standard/podoba.git",
|
|
9
|
+
"directory": "packages/react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/graphic-standard/podoba#readme",
|
|
12
|
+
"bugs": "https://github.com/graphic-standard/podoba/issues",
|
|
6
13
|
"main": "./src/index.ts",
|
|
7
14
|
"types": "./src/index.ts",
|
|
8
15
|
"exports": {
|
|
9
16
|
".": "./src/index.ts"
|
|
10
17
|
},
|
|
11
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
12
21
|
"publishConfig": {
|
|
13
22
|
"access": "public"
|
|
14
23
|
},
|
|
@@ -16,24 +25,24 @@
|
|
|
16
25
|
"typecheck": "tsc --build"
|
|
17
26
|
},
|
|
18
27
|
"dependencies": {
|
|
19
|
-
"@podoba/tokens": "
|
|
20
|
-
"@podoba/tailwind": "
|
|
21
|
-
"react-aria-components": "
|
|
22
|
-
"class-variance-authority": "
|
|
23
|
-
"clsx": "
|
|
24
|
-
"tailwind-merge": "
|
|
25
|
-
"@radix-ui/react-slot": "
|
|
28
|
+
"@podoba/tokens": "^0.0.4",
|
|
29
|
+
"@podoba/tailwind": "^0.0.4",
|
|
30
|
+
"react-aria-components": "1.18.0",
|
|
31
|
+
"class-variance-authority": "0.7.1",
|
|
32
|
+
"clsx": "2.1.1",
|
|
33
|
+
"tailwind-merge": "3.6.0",
|
|
34
|
+
"@radix-ui/react-slot": "1.2.4"
|
|
26
35
|
},
|
|
27
36
|
"peerDependencies": {
|
|
28
37
|
"react": ">=19",
|
|
29
38
|
"react-dom": ">=19"
|
|
30
39
|
},
|
|
31
40
|
"devDependencies": {
|
|
32
|
-
"react": "
|
|
33
|
-
"react-dom": "
|
|
34
|
-
"@types/react": "
|
|
35
|
-
"@types/react-dom": "
|
|
36
|
-
"typescript": "
|
|
37
|
-
"@types/bun": "
|
|
41
|
+
"react": "^19.2.1",
|
|
42
|
+
"react-dom": "^19.2.1",
|
|
43
|
+
"@types/react": "^19.2.7",
|
|
44
|
+
"@types/react-dom": "^19.2.3",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"@types/bun": "^1.3.3"
|
|
38
47
|
}
|
|
39
48
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button as RACButton,
|
|
4
|
+
ComboBox as RACComboBox,
|
|
5
|
+
type ComboBoxProps as RACComboBoxProps,
|
|
6
|
+
FieldError,
|
|
7
|
+
Input as RACInput,
|
|
8
|
+
Label,
|
|
9
|
+
ListBox,
|
|
10
|
+
ListBoxItem,
|
|
11
|
+
type ListBoxItemProps,
|
|
12
|
+
Popover,
|
|
13
|
+
Text,
|
|
14
|
+
} from 'react-aria-components'
|
|
15
|
+
import { uic } from '../utils/uic'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ComboBox — a filterable single-select: a text input that narrows a listbox as
|
|
19
|
+
* you type. Built on React Aria Components `ComboBox` (typeahead, keyboard nav,
|
|
20
|
+
* ARIA all handled). Styling mirrors our `Input` (white filled field) and shares
|
|
21
|
+
* `Select`'s cream dropdown, so the form controls stay visually consistent. Pass
|
|
22
|
+
* options as `ComboBoxItem` children.
|
|
23
|
+
*/
|
|
24
|
+
const Chevron = () => (
|
|
25
|
+
<svg width="9.5" height="9.5" viewBox="0 0 12 12" fill="none" aria-hidden="true" className="text-fg">
|
|
26
|
+
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// Same filled-field skin as input.tsx, with room on the right for the toggle.
|
|
31
|
+
// Invalid is owned by the ComboBox root, so it comes through `group-data-invalid`.
|
|
32
|
+
const ComboBoxInput = uic(RACInput, {
|
|
33
|
+
displayName: 'ComboBoxInput',
|
|
34
|
+
baseClass:
|
|
35
|
+
'h-12 w-full rounded-lg border border-border bg-surface pl-4 pr-11 text-sm text-fg ' +
|
|
36
|
+
'outline-none transition-colors placeholder:text-fg-muted ' +
|
|
37
|
+
'data-[hovered]:border-fg-subtle ' +
|
|
38
|
+
'data-[focused]:border-brand-green data-[focused]:ring-2 data-[focused]:ring-ring ' +
|
|
39
|
+
'group-data-[invalid]:border-danger group-data-[invalid]:ring-2 group-data-[invalid]:ring-danger ' +
|
|
40
|
+
'data-[disabled]:bg-surface-muted data-[disabled]:opacity-60',
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const ComboBoxItem = uic(ListBoxItem, {
|
|
44
|
+
displayName: 'ComboBoxItem',
|
|
45
|
+
// Matches SelectItem so the two dropdowns are indistinguishable.
|
|
46
|
+
baseClass:
|
|
47
|
+
'flex cursor-pointer select-none items-center rounded-md px-5 py-1 text-sm text-fg outline-none ' +
|
|
48
|
+
'data-[focused]:bg-surface-muted data-[selected]:font-medium ' +
|
|
49
|
+
'data-[disabled]:opacity-50 data-[disabled]:pointer-events-none',
|
|
50
|
+
}) as (props: ListBoxItemProps) => ReactNode
|
|
51
|
+
|
|
52
|
+
export type ComboBoxProps<T extends object> = RACComboBoxProps<T> & {
|
|
53
|
+
/** Visible label (required for accessibility). */
|
|
54
|
+
label: ReactNode
|
|
55
|
+
description?: ReactNode
|
|
56
|
+
errorMessage?: string
|
|
57
|
+
/** Placeholder shown in the empty input. */
|
|
58
|
+
placeholder?: string
|
|
59
|
+
children: ReactNode
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const ComboBox = <T extends object>({
|
|
63
|
+
label,
|
|
64
|
+
description,
|
|
65
|
+
errorMessage,
|
|
66
|
+
placeholder,
|
|
67
|
+
children,
|
|
68
|
+
...props
|
|
69
|
+
}: ComboBoxProps<T>) => (
|
|
70
|
+
// `menuTrigger="focus"` opens the list on focus/click (not only on typing), so
|
|
71
|
+
// the options are always one interaction away. Consumers can override via props.
|
|
72
|
+
<RACComboBox menuTrigger="focus" {...props} className="group flex flex-col gap-2">
|
|
73
|
+
<Label className="text-heading5 font-medium text-fg">{label}</Label>
|
|
74
|
+
<div className="relative">
|
|
75
|
+
<ComboBoxInput placeholder={placeholder} />
|
|
76
|
+
{/* RAC uses this Button to toggle the listbox open/closed. */}
|
|
77
|
+
<RACButton className="absolute inset-y-0 right-0 flex w-11 items-center justify-center rounded-r-lg outline-none data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring">
|
|
78
|
+
<Chevron />
|
|
79
|
+
</RACButton>
|
|
80
|
+
</div>
|
|
81
|
+
{description ? (
|
|
82
|
+
<Text slot="description" className="text-xs text-fg-muted">
|
|
83
|
+
{description}
|
|
84
|
+
</Text>
|
|
85
|
+
) : null}
|
|
86
|
+
<FieldError className="text-xs text-danger">{errorMessage}</FieldError>
|
|
87
|
+
<Popover className="min-w-[var(--trigger-width)] overflow-hidden rounded-lg bg-surface-card shadow-lg">
|
|
88
|
+
<ListBox
|
|
89
|
+
className="flex max-h-64 flex-col gap-3 overflow-auto py-5 outline-none"
|
|
90
|
+
renderEmptyState={() => <div className="px-5 py-1 text-sm text-fg-muted">No results</div>}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</ListBox>
|
|
94
|
+
</Popover>
|
|
95
|
+
</RACComboBox>
|
|
96
|
+
)
|
|
@@ -127,6 +127,10 @@ export const Dialog = ({
|
|
|
127
127
|
// Either way the RACDialog render-prop `close` resolves against the active
|
|
128
128
|
// overlay state, so `close()` works in both modes.
|
|
129
129
|
<Overlay
|
|
130
|
+
// Full-screen is an immersive takeover: swap the light 2px scrim for a
|
|
131
|
+
// heavier backdrop blur so the page behind reads as clearly blurred around
|
|
132
|
+
// the near-fullscreen canvas. (tailwind-merge dedupes the base blur/scrim.)
|
|
133
|
+
className={isFlex ? "bg-black/25 backdrop-blur-lg" : undefined}
|
|
130
134
|
isOpen={isOpen}
|
|
131
135
|
defaultOpen={defaultOpen}
|
|
132
136
|
onOpenChange={onOpenChange}
|
package/src/components/input.tsx
CHANGED
|
@@ -20,17 +20,20 @@ import { uic } from '../utils/uic'
|
|
|
20
20
|
*/
|
|
21
21
|
const StyledInput = uic(RACInput, {
|
|
22
22
|
displayName: 'InputControl',
|
|
23
|
-
//
|
|
24
|
-
// #
|
|
23
|
+
// White fill so the field reads as editable. The gs original used a cream
|
|
24
|
+
// (#f7f6f2 → surface-card) fill, but our Card surface is ALSO surface-card, so
|
|
25
|
+
// a filled field inside a card vanished and read as disabled. Inverted: active
|
|
26
|
+
// = white (surface), disabled = the muted cream. border #eceae1 → border ·
|
|
27
|
+
// hover #aba89c → fg-subtle · focus #75e7b8 → brand-green · error → danger.
|
|
25
28
|
// Identical to textarea.tsx's filled-field skin; `fieldSize` adds the
|
|
26
29
|
// single-line height (gs sizes the field via padding only).
|
|
27
30
|
baseClass:
|
|
28
|
-
'w-full rounded-lg border border-border bg-surface
|
|
31
|
+
'w-full rounded-lg border border-border bg-surface px-4 text-sm text-fg ' +
|
|
29
32
|
'outline-none transition-colors duration-200 placeholder:text-fg-muted ' +
|
|
30
33
|
'data-[hovered]:border-fg-subtle ' +
|
|
31
34
|
'data-[focused]:border-brand-green data-[focused]:ring-2 data-[focused]:ring-ring ' +
|
|
32
35
|
'data-[invalid]:border-danger data-[invalid]:ring-danger ' +
|
|
33
|
-
'data-[disabled]:opacity-
|
|
36
|
+
'data-[disabled]:bg-surface-muted data-[disabled]:opacity-60 data-[disabled]:pointer-events-none',
|
|
34
37
|
variants: {
|
|
35
38
|
// `fieldSize` (not `size`) to avoid colliding with the native <input size>
|
|
36
39
|
// attribute, which RAC's Input inherits (a numeric prop).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type ReactNode, useId, useRef, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button as RACButton,
|
|
4
|
+
Label,
|
|
5
|
+
ListBox,
|
|
6
|
+
ListBoxItem,
|
|
7
|
+
type ListBoxItemProps,
|
|
8
|
+
Popover,
|
|
9
|
+
type Selection,
|
|
10
|
+
Text,
|
|
11
|
+
} from 'react-aria-components'
|
|
12
|
+
import { clsx } from 'clsx'
|
|
13
|
+
import { uic } from '../utils/uic'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* MultiSelect — a dropdown that selects several options at once.
|
|
17
|
+
*
|
|
18
|
+
* React Aria Components ships no multi-select control at 1.x, so this composes
|
|
19
|
+
* one from primitives: a `Select`-styled trigger (white filled field) plus a
|
|
20
|
+
* controlled `Popover` + `ListBox selectionMode="multiple"` (RAC gives the
|
|
21
|
+
* listbox ARIA + keyboard multi-selection). The trigger summarises the choice —
|
|
22
|
+
* up to two labels, then an "N selected" count. Options come as `{ id, label }`
|
|
23
|
+
* data (not children) because the trigger needs the value→label map. Selection
|
|
24
|
+
* is uncontrolled by default; pass `selectedKeys` + `onChange` to control it.
|
|
25
|
+
*/
|
|
26
|
+
const Chevron = () => (
|
|
27
|
+
<svg width="9.5" height="9.5" viewBox="0 0 12 12" fill="none" aria-hidden="true" className="shrink-0 text-fg">
|
|
28
|
+
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
29
|
+
</svg>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const Check = () => (
|
|
33
|
+
<svg
|
|
34
|
+
viewBox="0 0 16 16"
|
|
35
|
+
fill="none"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
className="h-4 w-4 shrink-0 text-fg opacity-0 group-data-[selected]:opacity-100"
|
|
38
|
+
>
|
|
39
|
+
<path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
|
40
|
+
</svg>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const MultiSelectItem = uic(ListBoxItem, {
|
|
44
|
+
displayName: 'MultiSelectItem',
|
|
45
|
+
// `group` so the leading Check can react to this item's data-selected.
|
|
46
|
+
baseClass:
|
|
47
|
+
'group flex cursor-pointer select-none items-center gap-2 rounded-md px-5 py-1 text-sm text-fg outline-none ' +
|
|
48
|
+
'data-[focused]:bg-surface-muted data-[selected]:font-medium ' +
|
|
49
|
+
'data-[disabled]:opacity-50 data-[disabled]:pointer-events-none',
|
|
50
|
+
}) as (props: ListBoxItemProps) => ReactNode
|
|
51
|
+
|
|
52
|
+
export type MultiSelectOption = { id: string; label: string; isDisabled?: boolean }
|
|
53
|
+
|
|
54
|
+
export type MultiSelectProps = {
|
|
55
|
+
/** Visible label (required for accessibility). */
|
|
56
|
+
label: ReactNode
|
|
57
|
+
description?: ReactNode
|
|
58
|
+
errorMessage?: string
|
|
59
|
+
/** Shown in the trigger when nothing is selected. */
|
|
60
|
+
placeholder: string
|
|
61
|
+
options: MultiSelectOption[]
|
|
62
|
+
/** Controlled selected ids. Omit for uncontrolled (see `defaultSelectedKeys`). */
|
|
63
|
+
selectedKeys?: Set<string>
|
|
64
|
+
/** Initial selection when uncontrolled. */
|
|
65
|
+
defaultSelectedKeys?: Iterable<string>
|
|
66
|
+
/** Fired with the full set of selected ids after each toggle. */
|
|
67
|
+
onChange?: (ids: Set<string>) => void
|
|
68
|
+
isDisabled?: boolean
|
|
69
|
+
isInvalid?: boolean
|
|
70
|
+
className?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const MultiSelect = ({
|
|
74
|
+
label,
|
|
75
|
+
description,
|
|
76
|
+
errorMessage,
|
|
77
|
+
placeholder,
|
|
78
|
+
options,
|
|
79
|
+
selectedKeys,
|
|
80
|
+
defaultSelectedKeys,
|
|
81
|
+
onChange,
|
|
82
|
+
isDisabled,
|
|
83
|
+
isInvalid,
|
|
84
|
+
className,
|
|
85
|
+
}: MultiSelectProps) => {
|
|
86
|
+
const labelId = useId()
|
|
87
|
+
const triggerRef = useRef<HTMLButtonElement>(null)
|
|
88
|
+
const [open, setOpen] = useState(false)
|
|
89
|
+
const [internal, setInternal] = useState<Set<string>>(() => new Set(defaultSelectedKeys ?? []))
|
|
90
|
+
const selected = selectedKeys ?? internal
|
|
91
|
+
|
|
92
|
+
const handleChange = (keys: Selection) => {
|
|
93
|
+
const next = keys === 'all' ? new Set(options.map((o) => o.id)) : new Set([...keys].map(String))
|
|
94
|
+
if (selectedKeys === undefined) setInternal(next)
|
|
95
|
+
onChange?.(next)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const chosen = options.filter((o) => selected.has(o.id)).map((o) => o.label)
|
|
99
|
+
const summary = chosen.length === 0 ? placeholder : chosen.length <= 2 ? chosen.join(', ') : `${chosen.length} selected`
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className={clsx('flex flex-col gap-2', className)}>
|
|
103
|
+
<span id={labelId} className="text-heading5 font-medium text-fg">
|
|
104
|
+
{label}
|
|
105
|
+
</span>
|
|
106
|
+
<RACButton
|
|
107
|
+
ref={triggerRef}
|
|
108
|
+
aria-labelledby={labelId}
|
|
109
|
+
isDisabled={isDisabled}
|
|
110
|
+
onPress={() => setOpen(true)}
|
|
111
|
+
className={clsx(
|
|
112
|
+
'flex h-12 w-full items-center justify-between gap-2.5 rounded-lg border bg-surface px-4 text-sm text-fg outline-none transition-colors',
|
|
113
|
+
'data-[hovered]:border-fg-subtle data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring',
|
|
114
|
+
'data-[disabled]:bg-surface-muted data-[disabled]:opacity-60 data-[disabled]:pointer-events-none',
|
|
115
|
+
isInvalid ? 'border-danger ring-2 ring-danger' : 'border-border',
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<span className={clsx('truncate', chosen.length === 0 && 'text-fg-muted')}>{summary}</span>
|
|
119
|
+
<Chevron />
|
|
120
|
+
</RACButton>
|
|
121
|
+
{description ? <span className="text-xs text-fg-muted">{description}</span> : null}
|
|
122
|
+
{isInvalid && errorMessage ? <span className="text-xs text-danger">{errorMessage}</span> : null}
|
|
123
|
+
<Popover
|
|
124
|
+
triggerRef={triggerRef}
|
|
125
|
+
isOpen={open}
|
|
126
|
+
onOpenChange={setOpen}
|
|
127
|
+
className="min-w-[var(--trigger-width)] overflow-hidden rounded-lg bg-surface-card shadow-lg"
|
|
128
|
+
>
|
|
129
|
+
<ListBox
|
|
130
|
+
aria-labelledby={labelId}
|
|
131
|
+
selectionMode="multiple"
|
|
132
|
+
selectedKeys={selected}
|
|
133
|
+
onSelectionChange={handleChange}
|
|
134
|
+
className="flex max-h-64 flex-col gap-3 overflow-auto py-5 outline-none"
|
|
135
|
+
>
|
|
136
|
+
{options.map((o) => (
|
|
137
|
+
<MultiSelectItem key={o.id} id={o.id} textValue={o.label} isDisabled={o.isDisabled}>
|
|
138
|
+
<Check />
|
|
139
|
+
<span>{o.label}</span>
|
|
140
|
+
</MultiSelectItem>
|
|
141
|
+
))}
|
|
142
|
+
</ListBox>
|
|
143
|
+
</Popover>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -29,19 +29,17 @@ import { uic } from '../utils/uic'
|
|
|
29
29
|
*/
|
|
30
30
|
const SelectTrigger = uic(RACButton, {
|
|
31
31
|
displayName: 'SelectTrigger',
|
|
32
|
-
//
|
|
33
|
-
// 8px radius
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// (gs uses a box-shadow ring since the trigger has no border to colour); the
|
|
37
|
-
// ring is driven off the RACSelect root's `data-invalid` via `group-`.
|
|
32
|
+
// Sized to match the other form fields (Input / ComboBox): h-12, 16px side
|
|
33
|
+
// padding, 8px radius. White fill + border so the trigger doesn't vanish inside
|
|
34
|
+
// a Card (also surface-card) and read as disabled; hover darkens the border.
|
|
35
|
+
// Error → 2px danger ring driven off the RACSelect root's `data-invalid`.
|
|
38
36
|
baseClass:
|
|
39
|
-
'flex
|
|
37
|
+
'flex h-12 w-full items-center justify-between gap-2.5 rounded-lg border border-border bg-surface px-4 ' +
|
|
40
38
|
'text-sm text-fg outline-none transition-colors ' +
|
|
41
|
-
'data-[hovered]:
|
|
39
|
+
'data-[hovered]:border-fg-subtle ' +
|
|
42
40
|
'data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring ' +
|
|
43
|
-
'group-data-[invalid]:ring-2 group-data-[invalid]:ring-danger ' +
|
|
44
|
-
'data-[disabled]:opacity-
|
|
41
|
+
'group-data-[invalid]:border-danger group-data-[invalid]:ring-2 group-data-[invalid]:ring-danger ' +
|
|
42
|
+
'data-[disabled]:bg-surface-muted data-[disabled]:opacity-60 data-[disabled]:pointer-events-none',
|
|
45
43
|
})
|
|
46
44
|
|
|
47
45
|
export const SelectItem = uic(ListBoxItem, {
|
|
@@ -50,7 +48,7 @@ export const SelectItem = uic(ListBoxItem, {
|
|
|
50
48
|
// medium weight. We add a `surface-muted` focus background (gs highlights with
|
|
51
49
|
// weight only) so keyboard focus stays clearly visible on the cream content.
|
|
52
50
|
baseClass:
|
|
53
|
-
'flex cursor-pointer select-none items-center rounded-md px-5 text-sm text-fg outline-none ' +
|
|
51
|
+
'flex cursor-pointer select-none items-center rounded-md px-5 py-1 text-sm text-fg outline-none ' +
|
|
54
52
|
'data-[focused]:bg-surface-muted data-[selected]:font-medium ' +
|
|
55
53
|
'data-[disabled]:opacity-50 data-[disabled]:pointer-events-none',
|
|
56
54
|
}) as (props: ListBoxItemProps) => ReactNode
|
|
@@ -20,17 +20,19 @@ import { uic } from '../utils/uic'
|
|
|
20
20
|
*/
|
|
21
21
|
const StyledTextArea = uic(RACTextArea, {
|
|
22
22
|
displayName: 'TextAreaControl',
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
23
|
+
// White fill so the field reads as editable — matches input.tsx. The gs
|
|
24
|
+
// original used cream (surface-card), but our Card surface is also surface-card,
|
|
25
|
+
// so a field inside a card vanished / looked disabled. Inverted: active = white
|
|
26
|
+
// (surface), disabled = muted cream. border #eceae1 → border · hover #aba89c →
|
|
27
|
+
// fg-subtle · focus #75e7b8 → brand-green · error → danger. min-h-[120px] is a
|
|
28
|
+
// control dimension (not a design token) — gs uses a literal 120px here too.
|
|
27
29
|
baseClass:
|
|
28
|
-
'min-h-[120px] w-full resize-y rounded-lg border border-border bg-surface
|
|
30
|
+
'min-h-[120px] w-full resize-y rounded-lg border border-border bg-surface px-4 py-3 text-sm text-fg ' +
|
|
29
31
|
'outline-none transition-colors duration-200 placeholder:text-fg-muted ' +
|
|
30
32
|
'data-[hovered]:border-fg-subtle ' +
|
|
31
33
|
'data-[focused]:border-brand-green data-[focused]:ring-2 data-[focused]:ring-ring ' +
|
|
32
34
|
'data-[invalid]:border-danger data-[invalid]:ring-danger ' +
|
|
33
|
-
'data-[disabled]:opacity-
|
|
35
|
+
'data-[disabled]:bg-surface-muted data-[disabled]:opacity-60 data-[disabled]:pointer-events-none',
|
|
34
36
|
})
|
|
35
37
|
|
|
36
38
|
export type TextareaProps = TextFieldProps & {
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,8 @@ export * from "./components/checkbox";
|
|
|
16
16
|
export * from "./components/radio";
|
|
17
17
|
export * from "./components/switch";
|
|
18
18
|
export * from "./components/select";
|
|
19
|
+
export * from "./components/combobox";
|
|
20
|
+
export * from "./components/multiselect";
|
|
19
21
|
export * from "./components/dialog";
|
|
20
22
|
export * from "./components/dropdown-menu";
|
|
21
23
|
export * from "./components/context-menu";
|