untitledui 0.1.2 → 0.1.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/dist/index.mjs +19 -0
- package/package.json +10 -8
- package/templates/default/package.json +20 -21
- package/templates/default/src/app/home-screen.tsx +2 -3
- package/templates/default/src/app/layout.tsx +5 -13
- package/templates/default/src/components/marketing/header-navigation/dropdown-header-navigation.tsx +1 -1
- package/templates/default/src/components/shared/buttons/button.tsx +2 -1
- package/templates/default/src/components/shared/dropdown/dropdown.tsx +27 -18
- package/templates/default/src/components/shared/form/hook-form.tsx +75 -0
- package/templates/default/src/components/shared/{inputs/input → input}/index.tsx +3 -3
- package/templates/default/src/components/shared/{inputs/input → input}/input-payment.tsx +4 -4
- package/templates/default/src/components/shared/{inputs/input → input}/input-with-button.tsx +4 -4
- package/templates/default/src/components/shared/{inputs/input → input}/input-with-dropdown.tsx +4 -4
- package/templates/default/src/components/shared/{inputs/input → input}/input-with-prefix.tsx +4 -4
- package/templates/default/src/components/shared/progress-indicators/progress-indicators.tsx +2 -2
- package/templates/default/src/components/shared/{input-dropdown → select}/combobox.tsx +9 -9
- package/templates/default/src/components/shared/{input-dropdown → select}/multi-select.tsx +166 -166
- package/templates/default/src/components/shared/{input-dropdown → select}/popover.tsx +2 -2
- package/templates/default/src/components/shared/select/select-item.tsx +70 -0
- package/templates/default/src/components/shared/{input-dropdown/select.tsx → select/select-native.tsx} +2 -2
- package/templates/default/src/components/shared/select/select.tsx +143 -0
- package/templates/default/src/components/shared/slider/slider.tsx +2 -2
- package/templates/default/src/components/shared/{inputs/textarea → textarea}/textarea.tsx +2 -2
- package/templates/default/src/providers/theme.tsx +1 -1
- package/templates/default/src/styles/globals.css +3 -1
- package/templates/default/src/styles/theme.css +392 -380
- package/templates/default/src/styles/typography.css +20 -20
- package/dist/commands/add.js +0 -339
- package/dist/commands/init.js +0 -436
- package/dist/helper/download-tar-api.js +0 -129
- package/dist/helper/download-tar.js +0 -81
- package/dist/helper/find-css-file.js +0 -19
- package/dist/helper/formatText.js +0 -37
- package/dist/helper/get-components-api.js +0 -47
- package/dist/helper/get-components-list.js +0 -62
- package/dist/helper/get-components.js +0 -19
- package/dist/helper/get-config.js +0 -163
- package/dist/helper/get-package-info.js +0 -99
- package/dist/helper/get-pkg-manager.js +0 -16
- package/dist/helper/get-project.js +0 -176
- package/dist/helper/install-template.js +0 -29
- package/dist/helper/match-color-css.js +0 -82
- package/dist/helper/update-color-css.js +0 -134
- package/dist/index.js +0 -25
- package/dist/package.json +0 -50
- package/dist/res/components.json +0 -520
- package/dist/res/config.json +0 -3
- package/templates/default/src/components/shared/input-dropdown/dropdown-item.tsx +0 -98
- package/templates/default/src/components/shared/input-dropdown/input-dropdown.tsx +0 -172
- package/templates/default/src/fonts/GeistMonoVF.woff +0 -0
- package/templates/default/src/fonts/GeistVF.woff +0 -0
- package/templates/default/src/styles/colors.css +0 -805
- /package/templates/default/src/components/shared/{inputs → file-upload-trigger}/file-upload-trigger.tsx +0 -0
- /package/templates/default/src/components/shared/{inputs/form → form}/form.tsx +0 -0
- /package/templates/default/src/components/shared/{inputs → input}/hint-text.tsx +0 -0
- /package/templates/default/src/components/shared/{inputs → input}/label.tsx +0 -0
|
@@ -20,128 +20,12 @@ import { cx } from "@/components/utils";
|
|
|
20
20
|
import { useResizeObserver } from "@/hooks/use-resize-observer";
|
|
21
21
|
import Avatar from "../avatar/avatar";
|
|
22
22
|
import type { IconComponentType } from "../badges/badge-types";
|
|
23
|
-
import HintText from "../
|
|
24
|
-
import Label from "../
|
|
23
|
+
import HintText from "../input/hint-text";
|
|
24
|
+
import Label from "../input/label";
|
|
25
25
|
import { TagCloseX } from "../tags/base-components/tag-close-x";
|
|
26
|
-
import DropdownItem from "./dropdown-item";
|
|
27
|
-
import { type SelectValueType, sizes } from "./input-dropdown";
|
|
28
26
|
import { Popover } from "./popover";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
size: "sm" | "md";
|
|
32
|
-
selectedKeys: Key[];
|
|
33
|
-
selectedItems: ListData<SelectValueType>;
|
|
34
|
-
onRemove: (keys: Set<Key>) => void;
|
|
35
|
-
onInputChange: (value: string) => void;
|
|
36
|
-
}>({
|
|
37
|
-
size: "sm",
|
|
38
|
-
selectedKeys: [],
|
|
39
|
-
selectedItems: {} as ListData<SelectValueType>,
|
|
40
|
-
onRemove: () => {},
|
|
41
|
-
onInputChange: () => {},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const InnerComboBox = ({ isDisabled, shortcut, shortcutClassName, placeholder }: ComboBoxValueProps) => {
|
|
45
|
-
const focusManager = useFocusManager();
|
|
46
|
-
const selectContext = useContext(ComboboxContext);
|
|
47
|
-
|
|
48
|
-
const handleInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
49
|
-
const isCaretAtStart = event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0;
|
|
50
|
-
|
|
51
|
-
if (!isCaretAtStart && event.currentTarget.value !== "") {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
switch (event.key) {
|
|
56
|
-
case "Backspace":
|
|
57
|
-
case "ArrowLeft":
|
|
58
|
-
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
59
|
-
break;
|
|
60
|
-
case "ArrowRight":
|
|
61
|
-
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const handleTagKeyDown = (event: KeyboardEvent<HTMLButtonElement>, value: Key) => {
|
|
67
|
-
event.preventDefault();
|
|
68
|
-
|
|
69
|
-
const isFirstTag = selectContext?.selectedItems?.items?.[0]?.value === value;
|
|
70
|
-
|
|
71
|
-
switch (event.key) {
|
|
72
|
-
case " ":
|
|
73
|
-
case "Enter":
|
|
74
|
-
case "Backspace":
|
|
75
|
-
if (isFirstTag) {
|
|
76
|
-
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
77
|
-
} else {
|
|
78
|
-
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
selectContext.onRemove(new Set([value]));
|
|
82
|
-
break;
|
|
83
|
-
|
|
84
|
-
case "ArrowLeft":
|
|
85
|
-
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
86
|
-
break;
|
|
87
|
-
case "ArrowRight":
|
|
88
|
-
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const isSelectionEmpty = selectContext?.selectedItems?.items?.length === 0;
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div className="relative flex w-full flex-1 flex-row flex-wrap items-center justify-start gap-1.5">
|
|
97
|
-
{!isSelectionEmpty &&
|
|
98
|
-
// TODO: Use <TagList /> here
|
|
99
|
-
selectContext?.selectedItems?.items?.map((value) => (
|
|
100
|
-
<span key={value.value} className="flex items-center rounded-md bg-primary py-0.5 pr-1 pl-[5px] ring-1 ring-border-primary ring-inset">
|
|
101
|
-
<Avatar size="xxs" alt={value?.label} src={value?.avatarUrl} />
|
|
102
|
-
|
|
103
|
-
<p className="ml-[5px] truncate tt-sm-md whitespace-nowrap text-secondary select-none">{value?.label}</p>
|
|
104
|
-
|
|
105
|
-
<TagCloseX
|
|
106
|
-
size="md"
|
|
107
|
-
isDisabled={isDisabled}
|
|
108
|
-
className="ml-[3px]"
|
|
109
|
-
// For workaround, onKeyDown is added to the button
|
|
110
|
-
onKeyDown={(event) => handleTagKeyDown(event, value.value)}
|
|
111
|
-
onPress={() => selectContext.onRemove(new Set([value.value]))}
|
|
112
|
-
/>
|
|
113
|
-
</span>
|
|
114
|
-
))}
|
|
115
|
-
|
|
116
|
-
<div className={cx("relative flex min-w-[20%] flex-1 flex-row items-center", !isSelectionEmpty && "ml-0.5", shortcut && "min-w-[30%]")}>
|
|
117
|
-
<AriaInput
|
|
118
|
-
placeholder={placeholder}
|
|
119
|
-
onKeyDown={handleInputKeyDown}
|
|
120
|
-
className="w-full flex-[1_0_0] appearance-none bg-transparent tt-md text-ellipsis text-primary caret-alpha-black/90 placeholder:text-placeholder focus:outline-hidden disabled:cursor-not-allowed disabled:text-disabled disabled:placeholder:text-disabled"
|
|
121
|
-
/>
|
|
122
|
-
|
|
123
|
-
{shortcut && (
|
|
124
|
-
<div
|
|
125
|
-
aria-hidden="true"
|
|
126
|
-
className={cx(
|
|
127
|
-
"absolute inset-y-0.5 right-0.5 z-10 flex items-center rounded-r-[inherit] bg-linear-to-r from-transparent to-bg-primary to-40% pl-8",
|
|
128
|
-
shortcutClassName,
|
|
129
|
-
)}
|
|
130
|
-
>
|
|
131
|
-
<span
|
|
132
|
-
className={cx(
|
|
133
|
-
"pointer-events-none rounded px-1 py-px tt-xs-md text-quaternary ring-1 ring-border-secondary select-none ring-inset",
|
|
134
|
-
isDisabled && "bg-transparent text-disabled",
|
|
135
|
-
)}
|
|
136
|
-
>
|
|
137
|
-
⌘K
|
|
138
|
-
</span>
|
|
139
|
-
</div>
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
);
|
|
144
|
-
};
|
|
27
|
+
import { type SelectItemType, sizes } from "./select";
|
|
28
|
+
import SelectItem from "./select-item";
|
|
145
29
|
|
|
146
30
|
interface ComboBoxValueProps extends RefAttributes<HTMLDivElement> {
|
|
147
31
|
size: "sm" | "md";
|
|
@@ -154,43 +38,19 @@ interface ComboBoxValueProps extends RefAttributes<HTMLDivElement> {
|
|
|
154
38
|
onPointerEnter?: PointerEventHandler;
|
|
155
39
|
}
|
|
156
40
|
|
|
157
|
-
|
|
158
|
-
size
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<AriaGroup
|
|
172
|
-
{...otherProps}
|
|
173
|
-
className={({ isFocusWithin, isDisabled }) =>
|
|
174
|
-
cx(
|
|
175
|
-
"relative flex w-full items-center gap-2 rounded-lg bg-primary shadow-xs ring-1 ring-border-primary outline-hidden transition duration-200 ease-in-out ring-inset",
|
|
176
|
-
isDisabled && "cursor-not-allowed bg-disabled_subtle",
|
|
177
|
-
isFocusWithin && "ring-2 ring-border-brand",
|
|
178
|
-
sizes[size].root,
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
>
|
|
182
|
-
{Icon && (
|
|
183
|
-
<AriaButton>
|
|
184
|
-
<Icon className="size-5 text-fg-quaternary" />
|
|
185
|
-
</AriaButton>
|
|
186
|
-
)}
|
|
187
|
-
|
|
188
|
-
<FocusScope contain={false} autoFocus={false} restoreFocus={false}>
|
|
189
|
-
<InnerComboBox size={size} isDisabled={isDisabled} shortcut={shortcut} shortcutClassName={shortcutClassName} placeholder={placeholder} />
|
|
190
|
-
</FocusScope>
|
|
191
|
-
</AriaGroup>
|
|
192
|
-
);
|
|
193
|
-
};
|
|
41
|
+
const ComboboxContext = createContext<{
|
|
42
|
+
size: "sm" | "md";
|
|
43
|
+
selectedKeys: Key[];
|
|
44
|
+
selectedItems: ListData<SelectItemType>;
|
|
45
|
+
onRemove: (keys: Set<Key>) => void;
|
|
46
|
+
onInputChange: (value: string) => void;
|
|
47
|
+
}>({
|
|
48
|
+
size: "sm",
|
|
49
|
+
selectedKeys: [],
|
|
50
|
+
selectedItems: {} as ListData<SelectItemType>,
|
|
51
|
+
onRemove: () => {},
|
|
52
|
+
onInputChange: () => {},
|
|
53
|
+
});
|
|
194
54
|
|
|
195
55
|
interface CommonProps {
|
|
196
56
|
hint?: string;
|
|
@@ -200,14 +60,14 @@ interface CommonProps {
|
|
|
200
60
|
placeholder?: string;
|
|
201
61
|
}
|
|
202
62
|
|
|
203
|
-
interface ComboBoxProps extends Omit<AriaComboBoxProps<
|
|
63
|
+
interface ComboBoxProps extends Omit<AriaComboBoxProps<SelectItemType>, "children" | "items">, RefAttributes<HTMLDivElement>, CommonProps {
|
|
204
64
|
shortcut?: boolean;
|
|
205
|
-
items?:
|
|
65
|
+
items?: SelectItemType[];
|
|
206
66
|
popoverClassName?: string;
|
|
207
67
|
shortcutClassName?: string;
|
|
208
|
-
selectedItems: ListData<
|
|
68
|
+
selectedItems: ListData<SelectItemType>;
|
|
209
69
|
placeholderIcon?: IconComponentType | null;
|
|
210
|
-
children: AriaListBoxProps<
|
|
70
|
+
children: AriaListBoxProps<SelectItemType>["children"];
|
|
211
71
|
onItemCleared?: (key: Key) => void;
|
|
212
72
|
onItemInserted?: (key: Key) => void;
|
|
213
73
|
}
|
|
@@ -226,11 +86,11 @@ export const ComboBox = ({
|
|
|
226
86
|
...props
|
|
227
87
|
}: ComboBoxProps) => {
|
|
228
88
|
const { contains } = useFilter({ sensitivity: "base" });
|
|
229
|
-
const selectedKeys = selectedItems.items.map((i) => i?.
|
|
89
|
+
const selectedKeys = selectedItems.items.map((i) => i?.id);
|
|
230
90
|
|
|
231
91
|
const filter = useCallback(
|
|
232
|
-
(item:
|
|
233
|
-
return !selectedKeys.includes(item.
|
|
92
|
+
(item: SelectItemType, filterText: string) => {
|
|
93
|
+
return !selectedKeys.includes(item.id) && contains(item.label, filterText);
|
|
234
94
|
},
|
|
235
95
|
[contains, selectedKeys],
|
|
236
96
|
);
|
|
@@ -364,10 +224,150 @@ export const ComboBox = ({
|
|
|
364
224
|
);
|
|
365
225
|
};
|
|
366
226
|
|
|
227
|
+
const InnerComboBox = ({ isDisabled, shortcut, shortcutClassName, placeholder }: ComboBoxValueProps) => {
|
|
228
|
+
const focusManager = useFocusManager();
|
|
229
|
+
const selectContext = useContext(ComboboxContext);
|
|
230
|
+
|
|
231
|
+
const handleInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
232
|
+
const isCaretAtStart = event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0;
|
|
233
|
+
|
|
234
|
+
if (!isCaretAtStart && event.currentTarget.value !== "") {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
switch (event.key) {
|
|
239
|
+
case "Backspace":
|
|
240
|
+
case "ArrowLeft":
|
|
241
|
+
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
242
|
+
break;
|
|
243
|
+
case "ArrowRight":
|
|
244
|
+
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const handleTagKeyDown = (event: KeyboardEvent<HTMLButtonElement>, value: Key) => {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
|
|
252
|
+
const isFirstTag = selectContext?.selectedItems?.items?.[0]?.id === value;
|
|
253
|
+
|
|
254
|
+
switch (event.key) {
|
|
255
|
+
case " ":
|
|
256
|
+
case "Enter":
|
|
257
|
+
case "Backspace":
|
|
258
|
+
if (isFirstTag) {
|
|
259
|
+
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
260
|
+
} else {
|
|
261
|
+
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
selectContext.onRemove(new Set([value]));
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case "ArrowLeft":
|
|
268
|
+
focusManager?.focusPrevious({ wrap: false, tabbable: false });
|
|
269
|
+
break;
|
|
270
|
+
case "ArrowRight":
|
|
271
|
+
focusManager?.focusNext({ wrap: false, tabbable: false });
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const isSelectionEmpty = selectContext?.selectedItems?.items?.length === 0;
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div className="relative flex w-full flex-1 flex-row flex-wrap items-center justify-start gap-1.5">
|
|
280
|
+
{!isSelectionEmpty &&
|
|
281
|
+
// TODO: Use <TagList /> here
|
|
282
|
+
selectContext?.selectedItems?.items?.map((value) => (
|
|
283
|
+
<span key={value.id} className="flex items-center rounded-md bg-primary py-0.5 pr-1 pl-[5px] ring-1 ring-border-primary ring-inset">
|
|
284
|
+
<Avatar size="xxs" alt={value?.label} src={value?.avatarUrl} />
|
|
285
|
+
|
|
286
|
+
<p className="ml-[5px] truncate tt-sm-md whitespace-nowrap text-secondary select-none">{value?.label}</p>
|
|
287
|
+
|
|
288
|
+
<TagCloseX
|
|
289
|
+
size="md"
|
|
290
|
+
isDisabled={isDisabled}
|
|
291
|
+
className="ml-[3px]"
|
|
292
|
+
// For workaround, onKeyDown is added to the button
|
|
293
|
+
onKeyDown={(event) => handleTagKeyDown(event, value.id)}
|
|
294
|
+
onPress={() => selectContext.onRemove(new Set([value.id]))}
|
|
295
|
+
/>
|
|
296
|
+
</span>
|
|
297
|
+
))}
|
|
298
|
+
|
|
299
|
+
<div className={cx("relative flex min-w-[20%] flex-1 flex-row items-center", !isSelectionEmpty && "ml-0.5", shortcut && "min-w-[30%]")}>
|
|
300
|
+
<AriaInput
|
|
301
|
+
placeholder={placeholder}
|
|
302
|
+
onKeyDown={handleInputKeyDown}
|
|
303
|
+
className="w-full flex-[1_0_0] appearance-none bg-transparent tt-md text-ellipsis text-primary caret-alpha-black/90 placeholder:text-placeholder focus:outline-hidden disabled:cursor-not-allowed disabled:text-disabled disabled:placeholder:text-disabled"
|
|
304
|
+
/>
|
|
305
|
+
|
|
306
|
+
{shortcut && (
|
|
307
|
+
<div
|
|
308
|
+
aria-hidden="true"
|
|
309
|
+
className={cx(
|
|
310
|
+
"absolute inset-y-0.5 right-0.5 z-10 flex items-center rounded-r-[inherit] bg-linear-to-r from-transparent to-bg-primary to-40% pl-8",
|
|
311
|
+
shortcutClassName,
|
|
312
|
+
)}
|
|
313
|
+
>
|
|
314
|
+
<span
|
|
315
|
+
className={cx(
|
|
316
|
+
"pointer-events-none rounded px-1 py-px tt-xs-md text-quaternary ring-1 ring-border-secondary select-none ring-inset",
|
|
317
|
+
isDisabled && "bg-transparent text-disabled",
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
⌘K
|
|
321
|
+
</span>
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export const ComboBoxTagsValue = ({
|
|
330
|
+
size,
|
|
331
|
+
isDisabled,
|
|
332
|
+
shortcut,
|
|
333
|
+
placeholder,
|
|
334
|
+
shortcutClassName,
|
|
335
|
+
placeholderIcon: Icon = SearchLg,
|
|
336
|
+
...otherProps
|
|
337
|
+
}: ComboBoxValueProps) => {
|
|
338
|
+
const state = useContext(ComboBoxStateContext);
|
|
339
|
+
|
|
340
|
+
useHotkeys("meta+k", () => state?.setOpen(true), { enabled: !isDisabled && shortcut });
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<AriaGroup
|
|
344
|
+
{...otherProps}
|
|
345
|
+
className={({ isFocusWithin, isDisabled }) =>
|
|
346
|
+
cx(
|
|
347
|
+
"relative flex w-full items-center gap-2 rounded-lg bg-primary shadow-xs ring-1 ring-border-primary outline-hidden transition duration-200 ease-in-out ring-inset",
|
|
348
|
+
isDisabled && "cursor-not-allowed bg-disabled_subtle",
|
|
349
|
+
isFocusWithin && "ring-2 ring-border-brand",
|
|
350
|
+
sizes[size].root,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
>
|
|
354
|
+
{Icon && (
|
|
355
|
+
<AriaButton>
|
|
356
|
+
<Icon className="size-5 text-fg-quaternary" />
|
|
357
|
+
</AriaButton>
|
|
358
|
+
)}
|
|
359
|
+
|
|
360
|
+
<FocusScope contain={false} autoFocus={false} restoreFocus={false}>
|
|
361
|
+
<InnerComboBox size={size} isDisabled={isDisabled} shortcut={shortcut} shortcutClassName={shortcutClassName} placeholder={placeholder} />
|
|
362
|
+
</FocusScope>
|
|
363
|
+
</AriaGroup>
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
367
|
const MultiSelect = ComboBox as typeof ComboBox & {
|
|
368
|
-
|
|
368
|
+
Item: typeof SelectItem;
|
|
369
369
|
};
|
|
370
370
|
|
|
371
|
-
MultiSelect.
|
|
371
|
+
MultiSelect.Item = SelectItem;
|
|
372
372
|
|
|
373
373
|
export { MultiSelect as MultiSelect };
|
|
@@ -23,9 +23,9 @@ export const Popover = (props: PopoverProps) => {
|
|
|
23
23
|
// scrollbar styles
|
|
24
24
|
// "[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-alpha-black/15 [&::-webkit-scrollbar-track]:rounded-full [&::-webkit-scrollbar-track]:bg-primary [&::-webkit-scrollbar]:w-2",
|
|
25
25
|
state.isEntering &&
|
|
26
|
-
"ease-out animate-in
|
|
26
|
+
"duration-150 ease-out animate-in fade-in placement-right:origin-left placement-right:slide-in-from-left-0.5 placement-top:origin-bottom placement-top:slide-in-from-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-in-from-top-0.5",
|
|
27
27
|
state.isExiting &&
|
|
28
|
-
"
|
|
28
|
+
"duration-100 ease-in animate-out fade-out placement-right:origin-left placement-right:slide-out-to-left-0.5 placement-top:origin-bottom placement-top:slide-out-to-bottom-0.5 placement-bottom:origin-top placement-bottom:slide-out-to-top-0.5",
|
|
29
29
|
props.size === "md" && "max-h-80!",
|
|
30
30
|
|
|
31
31
|
typeof props.className === "function" ? props.className(state) : props.className,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import { Check } from "@untitledui/icons";
|
|
5
|
+
import type { ListBoxItemProps as AriaListBoxItemProps } from "react-aria-components";
|
|
6
|
+
import { ListBoxItem as AriaListBoxItem, Text } from "react-aria-components";
|
|
7
|
+
import Avatar from "@/components/shared/avatar/avatar";
|
|
8
|
+
import { cx } from "@/components/utils";
|
|
9
|
+
import type { SelectItemType } from "./select";
|
|
10
|
+
import { SelectContext } from "./select";
|
|
11
|
+
|
|
12
|
+
const sizes = {
|
|
13
|
+
sm: "p-2 pr-2.5",
|
|
14
|
+
md: "p-2.5 pl-2",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface SelectItemProps extends Omit<AriaListBoxItemProps<SelectItemType>, "id">, SelectItemType {}
|
|
18
|
+
|
|
19
|
+
export const SelectItem = ({ label, id, value, avatarUrl, supportingText, isDisabled, icon: Icon, className, children, ...props }: SelectItemProps) => {
|
|
20
|
+
const { size } = useContext(SelectContext);
|
|
21
|
+
|
|
22
|
+
const textValue = supportingText ? label + " " + supportingText : label;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<AriaListBoxItem
|
|
26
|
+
id={id}
|
|
27
|
+
value={value as unknown as object}
|
|
28
|
+
textValue={textValue}
|
|
29
|
+
isDisabled={isDisabled}
|
|
30
|
+
{...props}
|
|
31
|
+
className={(state) => cx("w-full px-1.5 py-px outline-hidden", typeof className === "function" ? className(state) : className)}
|
|
32
|
+
>
|
|
33
|
+
{(state) => (
|
|
34
|
+
<div
|
|
35
|
+
className={cx(
|
|
36
|
+
"flex cursor-pointer items-center gap-2 rounded-md outline-hidden select-none",
|
|
37
|
+
state.isSelected && "bg-active",
|
|
38
|
+
state.isDisabled && "cursor-not-allowed",
|
|
39
|
+
state.isFocused && "bg-primary_hover",
|
|
40
|
+
state.isFocusVisible && "ring-2 ring-focus-ring ring-inset",
|
|
41
|
+
sizes[size],
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
{avatarUrl ? (
|
|
45
|
+
<Avatar aria-hidden="true" size="xs" src={avatarUrl} alt={label} />
|
|
46
|
+
) : Icon ? (
|
|
47
|
+
<Icon aria-hidden="true" className={cx("size-5 shrink-0 text-fg-quaternary", state.isDisabled && "text-fg-disabled")} />
|
|
48
|
+
) : null}
|
|
49
|
+
|
|
50
|
+
<section className="flex w-full min-w-0 flex-1 flex-wrap gap-x-2">
|
|
51
|
+
<Text slot="label" className={cx("truncate tt-md-md whitespace-nowrap text-primary", state.isDisabled && "text-disabled")}>
|
|
52
|
+
{label || (typeof children === "function" ? children(state) : children)}
|
|
53
|
+
</Text>
|
|
54
|
+
|
|
55
|
+
{supportingText && (
|
|
56
|
+
<Text slot="description" className={cx("tt-md whitespace-nowrap text-tertiary", state.isDisabled && "text-disabled")}>
|
|
57
|
+
{supportingText}
|
|
58
|
+
</Text>
|
|
59
|
+
)}
|
|
60
|
+
</section>
|
|
61
|
+
{state.isSelected && (
|
|
62
|
+
<Check className={cx("ml-auto size-5 text-fg-brand-primary", state.isDisabled && "text-fg-disabled")} aria-hidden="true" />
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</AriaListBoxItem>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default SelectItem;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { type ChangeEventHandler, useId } from "react";
|
|
4
4
|
import { ChevronDown } from "@untitledui/icons";
|
|
5
|
-
import HintText from "../
|
|
6
|
-
import Label from "../
|
|
5
|
+
import HintText from "../input/hint-text";
|
|
6
|
+
import Label from "../input/label";
|
|
7
7
|
|
|
8
8
|
export const NativeSelect = (props: {
|
|
9
9
|
label?: string;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode, Ref, RefAttributes } from "react";
|
|
4
|
+
import { createContext } from "react";
|
|
5
|
+
import { ChevronDown, User01 } from "@untitledui/icons";
|
|
6
|
+
import type { SelectProps as AriaSelectProps } from "react-aria-components";
|
|
7
|
+
import { Button as AriaButton, ListBox as AriaListBox, Select as AriaSelect, SelectValue as AriaSelectValue } from "react-aria-components";
|
|
8
|
+
import Avatar from "@/components/shared/avatar/avatar";
|
|
9
|
+
import type { IconComponentType } from "@/components/shared/badges/badge-types";
|
|
10
|
+
import HintText from "@/components/shared/input/hint-text";
|
|
11
|
+
import Label from "@/components/shared/input/label";
|
|
12
|
+
import { cx } from "@/components/utils";
|
|
13
|
+
import { ComboBox } from "./combobox";
|
|
14
|
+
import { Popover } from "./popover";
|
|
15
|
+
import Item from "./select-item";
|
|
16
|
+
|
|
17
|
+
export type SelectItemType = {
|
|
18
|
+
id: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
avatarUrl?: string;
|
|
21
|
+
isDisabled?: boolean;
|
|
22
|
+
supportingText?: string;
|
|
23
|
+
icon?: IconComponentType;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type Types = "default" | "iconLeading" | "avatarLeading" | "search" | "tags";
|
|
27
|
+
type SelectTypes = "default" | "iconLeading" | "avatarLeading";
|
|
28
|
+
|
|
29
|
+
export interface CommonProps {
|
|
30
|
+
hint?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
tooltip?: string;
|
|
33
|
+
size?: "sm" | "md";
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface SelectProps extends Omit<AriaSelectProps<SelectItemType>, "children" | "items">, RefAttributes<HTMLDivElement>, CommonProps {
|
|
38
|
+
type?: SelectTypes;
|
|
39
|
+
items?: SelectItemType[];
|
|
40
|
+
popoverClassName?: string;
|
|
41
|
+
placeholderIcon?: IconComponentType;
|
|
42
|
+
children: ReactNode | ((item: SelectItemType) => ReactNode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SelectValueProps {
|
|
46
|
+
isOpen: boolean;
|
|
47
|
+
size: "sm" | "md";
|
|
48
|
+
type: SelectTypes;
|
|
49
|
+
isFocused: boolean;
|
|
50
|
+
isDisabled: boolean;
|
|
51
|
+
placeholder?: string;
|
|
52
|
+
ref?: Ref<HTMLButtonElement>;
|
|
53
|
+
placeholderIcon?: IconComponentType;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const sizes = {
|
|
57
|
+
sm: { root: "py-2 px-3", shortcut: "pr-2.5" },
|
|
58
|
+
md: { root: "py-2.5 px-3.5", shortcut: "pr-3" },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const SelectValue = (props: SelectValueProps) => {
|
|
62
|
+
return (
|
|
63
|
+
<AriaButton
|
|
64
|
+
className={cx(
|
|
65
|
+
"relative flex w-full cursor-pointer items-center rounded-lg bg-primary shadow-xs ring-1 ring-border-primary outline-hidden transition duration-100 ease-linear ring-inset",
|
|
66
|
+
(props.isFocused || props.isOpen) && "ring-2 ring-border-brand",
|
|
67
|
+
props.isDisabled && "cursor-not-allowed bg-disabled_subtle text-disabled",
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
<AriaSelectValue<SelectItemType>
|
|
71
|
+
className={cx("flex h-max w-full items-center justify-start gap-2 truncate text-left align-middle", sizes[props.size].root)}
|
|
72
|
+
>
|
|
73
|
+
{(state) => {
|
|
74
|
+
const Icon = state?.selectedItem?.icon || props.placeholderIcon;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
{state?.selectedItem?.avatarUrl ? (
|
|
79
|
+
<Avatar size="xs" src={state.selectedItem.avatarUrl} alt={state.selectedItem.label} />
|
|
80
|
+
) : Icon ? (
|
|
81
|
+
<Icon aria-hidden="true" className={cx("size-5 shrink-0 text-fg-quaternary", props.isDisabled && "text-fg-disabled")} />
|
|
82
|
+
) : null}
|
|
83
|
+
|
|
84
|
+
{state.selectedItem ? (
|
|
85
|
+
<section className="flex w-full gap-2 truncate">
|
|
86
|
+
<p className="truncate tt-md-md text-primary">{state.selectedItem?.label}</p>
|
|
87
|
+
{state.selectedItem?.supportingText && <p className="tt-md text-tertiary">{state.selectedItem?.supportingText}</p>}
|
|
88
|
+
</section>
|
|
89
|
+
) : (
|
|
90
|
+
<p className={cx("tt-md text-placeholder", props.isDisabled && "text-disabled")}>{props.placeholder}</p>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
<ChevronDown size={20} aria-hidden="true" className="ml-auto shrink-0 text-fg-quaternary" />
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
}}
|
|
97
|
+
</AriaSelectValue>
|
|
98
|
+
</AriaButton>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const SelectContext = createContext<{ type: Types; size: "sm" | "md" }>({ type: "default", size: "sm" });
|
|
103
|
+
|
|
104
|
+
const Select = ({ type = "default", placeholder = "Select", placeholderIcon, size = "sm", children, items, label, hint, tooltip, ...rest }: SelectProps) => {
|
|
105
|
+
return (
|
|
106
|
+
<SelectContext.Provider value={{ type, size }}>
|
|
107
|
+
<AriaSelect {...rest}>
|
|
108
|
+
{(state) => (
|
|
109
|
+
<div className="flex flex-col gap-1.5">
|
|
110
|
+
{label && (
|
|
111
|
+
<Label isRequired={state.isRequired} tooltip={tooltip}>
|
|
112
|
+
{label}
|
|
113
|
+
</Label>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<SelectValue
|
|
117
|
+
{...state}
|
|
118
|
+
{...{ type, size, placeholder }}
|
|
119
|
+
placeholderIcon={type === "avatarLeading" || type === "iconLeading" ? placeholderIcon || User01 : undefined}
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<Popover size={size} className={rest.popoverClassName}>
|
|
123
|
+
<AriaListBox items={items} className="size-full outline-hidden">
|
|
124
|
+
{children}
|
|
125
|
+
</AriaListBox>
|
|
126
|
+
</Popover>
|
|
127
|
+
|
|
128
|
+
{hint && <HintText isInvalid={state.isInvalid}>{hint}</HintText>}
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
</AriaSelect>
|
|
132
|
+
</SelectContext.Provider>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const _Select = Select as typeof Select & {
|
|
137
|
+
ComboBox: typeof ComboBox;
|
|
138
|
+
Item: typeof Item;
|
|
139
|
+
};
|
|
140
|
+
_Select.ComboBox = ComboBox;
|
|
141
|
+
_Select.Item = Item;
|
|
142
|
+
|
|
143
|
+
export { _Select as Select };
|
|
@@ -8,7 +8,7 @@ const styles = sortCx({
|
|
|
8
8
|
default: "hidden",
|
|
9
9
|
bottom: "absolute top-2 left-1/2 -translate-x-1/2 translate-y-full text-md leading-md font-medium text-primary",
|
|
10
10
|
"top-floating":
|
|
11
|
-
"absolute -top-2 left-1/2 -translate-x-1/2 -translate-y-full rounded-lg bg-primary px-3 py-2 text-xs leading-xs font-semibold text-secondary shadow-lg ring-
|
|
11
|
+
"absolute -top-2 left-1/2 -translate-x-1/2 -translate-y-full rounded-lg bg-primary px-3 py-2 text-xs leading-xs font-semibold text-secondary ring-1 shadow-lg ring-border-secondary_alt",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
interface SliderProps extends AriaSliderProps {
|
|
@@ -55,7 +55,7 @@ export const Slider = ({
|
|
|
55
55
|
index={index}
|
|
56
56
|
className={({ isFocusVisible, isDragging }) =>
|
|
57
57
|
cx(
|
|
58
|
-
"absolute top-1/2 z-50 box-border size-6 cursor-grab rounded-full bg-slider-handle-bg shadow-md ring-
|
|
58
|
+
"absolute top-1/2 z-50 box-border size-6 cursor-grab rounded-full bg-slider-handle-bg ring-2 shadow-md ring-slider-handle-border ring-inset",
|
|
59
59
|
isFocusVisible && "outline-2 outline-offset-2 outline-focus-ring",
|
|
60
60
|
isDragging && "cursor-grabbing",
|
|
61
61
|
)
|
|
@@ -4,8 +4,8 @@ import type { ReactNode, Ref } from "react";
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
import type { TextAreaProps as AriaTextAreaProps, TextFieldProps as AriaTextFieldProps } from "react-aria-components";
|
|
6
6
|
import { TextArea as AriaTextArea } from "react-aria-components";
|
|
7
|
-
import HintText from "@/components/shared/
|
|
8
|
-
import Label from "@/components/shared/
|
|
7
|
+
import HintText from "@/components/shared/input/hint-text";
|
|
8
|
+
import Label from "@/components/shared/input/label";
|
|
9
9
|
import { cx } from "@/components/utils";
|
|
10
10
|
import { TextField } from "../input";
|
|
11
11
|
|
|
@@ -4,7 +4,7 @@ import { ThemeProvider } from "next-themes";
|
|
|
4
4
|
|
|
5
5
|
export function Theme({ children }: { children: React.ReactNode }) {
|
|
6
6
|
return (
|
|
7
|
-
<ThemeProvider attribute="
|
|
7
|
+
<ThemeProvider attribute="class" value={{ light: "light-mode", dark: "dark-mode" }} enableSystem>
|
|
8
8
|
{children}
|
|
9
9
|
</ThemeProvider>
|
|
10
10
|
);
|