@botiverse/raft-ui 0.0.0-stage → 0.7.0
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/CHANGELOG.md +561 -0
- package/LICENSE +21 -0
- package/README.md +103 -2
- package/codemods/package-name.mjs +166 -0
- package/codemods/token-names.mjs +150 -0
- package/dist/cn.d.mts +5 -0
- package/dist/cn.mjs +48 -0
- package/dist/components/combobox-select/combobox-select.css +286 -0
- package/dist/components/float-button/float-button.css +316 -0
- package/dist/components/steps/otter-indicator.css +141 -0
- package/dist/components/steps/steps.css +370 -0
- package/dist/components/timeline/timeline.css +255 -0
- package/dist/dialog-CY6S-Xqf.mjs +505 -0
- package/dist/fonts.css +13 -0
- package/dist/foundation.css +623 -0
- package/dist/index-B3_j9XYd.d.mts +329 -0
- package/dist/index.d.mts +5114 -0
- package/dist/index.mjs +16442 -0
- package/dist/style.css +1315 -0
- package/dist/styles.css +391 -0
- package/dist/tokens.json +3811 -0
- package/dist/wip.d.mts +92 -0
- package/dist/wip.mjs +517 -0
- package/package.json +92 -3
- package/skills/raft-ui-critique/SKILL.md +167 -0
- package/skills/raft-ui-critique/agents/openai.yaml +4 -0
- package/skills/raft-ui-critique/references/theme-lenses.md +69 -0
- package/skills/raft-ui-guide/SKILL.md +96 -0
- package/skills/raft-ui-guide/agents/openai.yaml +4 -0
- package/skills/raft-ui-guide/rules/adoption.md +55 -0
- package/skills/raft-ui-guide/rules/composition.md +14 -0
- package/skills/raft-ui-guide/rules/conversation.md +15 -0
- package/skills/raft-ui-guide/rules/display.md +14 -0
- package/skills/raft-ui-guide/rules/feedback.md +13 -0
- package/skills/raft-ui-guide/rules/forms.md +13 -0
- package/skills/raft-ui-guide/rules/layout.md +15 -0
- package/skills/raft-ui-guide/rules/lookup.md +78 -0
- package/skills/raft-ui-guide/rules/overlays.md +13 -0
- package/skills/raft-ui-guide/rules/overview.md +30 -0
- package/skills/raft-ui-guide/rules/styling.md +17 -0
- package/skills/raft-ui-guide/rules/technical-overview.md +80 -0
package/dist/wip.d.mts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { O as ButtonProps, T as DialogTrigger, i as DialogClose, s as DialogContentProps, t as Dialog, w as DialogTitleProps } from "./index-B3_j9XYd.mjs";
|
|
2
|
+
import { CSSProperties, ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
4
|
+
//#region src/components/combobox-select/combobox-select.types.d.ts
|
|
5
|
+
type ComboboxSelectValue = string | number;
|
|
6
|
+
interface ComboboxSelectOption {
|
|
7
|
+
value: ComboboxSelectValue;
|
|
8
|
+
label: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface ComboboxSelectGroup<Option = ComboboxSelectOption> {
|
|
12
|
+
type: "group";
|
|
13
|
+
key: string | number;
|
|
14
|
+
label: ReactNode;
|
|
15
|
+
children: readonly Option[];
|
|
16
|
+
}
|
|
17
|
+
type ComboboxSelectSelection<Multiple extends boolean | undefined> = Multiple extends true ? ComboboxSelectValue[] : ComboboxSelectValue | null;
|
|
18
|
+
interface ComboboxSelectMessages {
|
|
19
|
+
clear: string;
|
|
20
|
+
toggle: string;
|
|
21
|
+
selected: string;
|
|
22
|
+
chipDescription: string;
|
|
23
|
+
inputDescription: string;
|
|
24
|
+
remove: (label: string) => string;
|
|
25
|
+
}
|
|
26
|
+
type RootProps = Combobox.Root.Props<unknown, boolean>;
|
|
27
|
+
type ComboboxSelectProps<Option = ComboboxSelectOption, Multiple extends boolean | undefined = false> = Pick<RootProps, "name" | "form" | "required" | "disabled" | "readOnly" | "open" | "defaultOpen" | "onOpenChange" | "onOpenChangeComplete" | "autoHighlight" | "autoComplete" | "inputValue" | "defaultInputValue" | "onInputValueChange"> & Pick<ComponentPropsWithRef<"input">, "id" | "ref" | "aria-label" | "aria-labelledby" | "aria-describedby" | "aria-invalid"> & {
|
|
28
|
+
options: readonly (Option | ComboboxSelectGroup<Option>)[];
|
|
29
|
+
multiple?: Multiple;
|
|
30
|
+
value?: ComboboxSelectSelection<Multiple>;
|
|
31
|
+
defaultValue?: ComboboxSelectSelection<Multiple>;
|
|
32
|
+
onValueChange?: (value: ComboboxSelectSelection<Multiple>, options: Multiple extends true ? (Option | undefined)[] : Option | undefined | null, details: Combobox.Root.ChangeEventDetails) => void;
|
|
33
|
+
labelField?: keyof Option;
|
|
34
|
+
valueField?: keyof Option;
|
|
35
|
+
disabledField?: keyof Option;
|
|
36
|
+
/** Supplies labels for values that have not been loaded yet. */
|
|
37
|
+
fallbackOption?: (value: ComboboxSelectValue) => Option;
|
|
38
|
+
filterable?: boolean;
|
|
39
|
+
filter?: false | ((query: string, option: Option) => boolean);
|
|
40
|
+
/** Display the provided results without local filtering. Requests belong to the caller. */
|
|
41
|
+
remote?: boolean;
|
|
42
|
+
onSearch?: (query: string) => void;
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
create?: boolean;
|
|
45
|
+
/** Alias for create, useful when matching Select libraries that call this mode creatable. */
|
|
46
|
+
creatable?: boolean;
|
|
47
|
+
/** Called only when the creation row is selected. Return null to reject creation. */
|
|
48
|
+
onCreate?: (query: string) => Option | null;
|
|
49
|
+
clearable?: boolean;
|
|
50
|
+
showArrow?: boolean;
|
|
51
|
+
clearFilterAfterSelect?: boolean;
|
|
52
|
+
onClear?: () => void;
|
|
53
|
+
/** Collapse chips when the control is unfocused; all remain reachable when focused. */
|
|
54
|
+
maxTagCount?: number;
|
|
55
|
+
size?: "sm" | "md" | "lg";
|
|
56
|
+
status?: "error" | "warning";
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
renderLabel?: (option: Option) => ReactNode;
|
|
59
|
+
/** Customizes option content while preserving the Base UI option element. */
|
|
60
|
+
renderOption?: (option: Option, info: {
|
|
61
|
+
selected: boolean;
|
|
62
|
+
}) => ReactNode;
|
|
63
|
+
/** Customizes chip content while preserving keyboard navigation and removal. */
|
|
64
|
+
renderTag?: (option: Option) => ReactNode;
|
|
65
|
+
renderCreateLabel?: (query: string) => ReactNode;
|
|
66
|
+
emptyContent?: ReactNode;
|
|
67
|
+
loadingContent?: ReactNode;
|
|
68
|
+
messages?: Partial<ComboboxSelectMessages>;
|
|
69
|
+
inputProps?: Omit<Combobox.Input.Props, "value" | "defaultValue" | "size" | "children">;
|
|
70
|
+
portalProps?: Combobox.Portal.Props;
|
|
71
|
+
positionerProps?: Omit<Combobox.Positioner.Props, "children">;
|
|
72
|
+
popupProps?: Omit<Combobox.Popup.Props, "children">;
|
|
73
|
+
className?: string;
|
|
74
|
+
style?: CSSProperties;
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/components/combobox-select/combobox-select.d.ts
|
|
78
|
+
export declare function ComboboxSelect<Option = ComboboxSelectOption, Multiple extends boolean | undefined = false>({ options, multiple, value, defaultValue, onValueChange, labelField, valueField, disabledField, fallbackOption, filterable, filter, remote, onSearch, loading, create, creatable, onCreate, clearable, showArrow, clearFilterAfterSelect, onClear, maxTagCount, size, status, placeholder, renderLabel, renderOption, renderTag, renderCreateLabel, emptyContent, loadingContent, messages, inputProps, portalProps, positionerProps, popupProps, className, style, id: idProp, ref, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, inputValue, defaultInputValue, onInputValueChange, disabled, readOnly, autoHighlight, ...rootProps }: ComboboxSelectProps<Option, Multiple>): import("react").JSX.Element;
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/components/announcement-dialog/announcement-dialog.d.ts
|
|
81
|
+
export declare function AnnouncementDialogContent({ className, initialFocus, ref, ...props }: DialogContentProps): import("react").JSX.Element;
|
|
82
|
+
export declare function AnnouncementDialogBody({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
83
|
+
export declare function AnnouncementDialogHeader({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
84
|
+
export declare function AnnouncementDialogLabel({ className, children, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
85
|
+
export declare function AnnouncementDialogTitle({ className, ...props }: DialogTitleProps): import("react").JSX.Element;
|
|
86
|
+
export declare function AnnouncementDialogPicture({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
87
|
+
export declare function AnnouncementDialogText({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
88
|
+
export declare function AnnouncementDialogFooter({ className, ...props }: ComponentPropsWithRef<"div">): import("react").JSX.Element;
|
|
89
|
+
export declare function AnnouncementDialogAction({ className, ...props }: Omit<ButtonProps, "variant">): import("react").JSX.Element;
|
|
90
|
+
export declare function AnnouncementDialogSecondaryAction({ className, ...props }: Omit<ButtonProps, "variant">): import("react").JSX.Element;
|
|
91
|
+
//#endregion
|
|
92
|
+
export { Dialog as AnnouncementDialog, DialogClose as AnnouncementDialogClose, DialogTrigger as AnnouncementDialogTrigger, type ComboboxSelectGroup, type ComboboxSelectMessages, type ComboboxSelectOption, type ComboboxSelectProps, type ComboboxSelectSelection, type ComboboxSelectValue };
|
package/dist/wip.mjs
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { S as tv, b as useTheme, f as DialogTitle, i as DialogContent, m as Button, p as DialogTrigger, r as DialogClose, t as Dialog, y as useThemeScopeProps } from "./dialog-CY6S-Xqf.mjs";
|
|
2
|
+
import { Check, ChevronDown, LoaderCircle, Plus, X } from "lucide-react";
|
|
3
|
+
import { useCallback, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
6
|
+
//#region src/components/combobox-select/combobox-select.options.ts
|
|
7
|
+
function isOptionGroup(option) {
|
|
8
|
+
return typeof option === "object" && option !== null && "type" in option && option.type === "group" && "children" in option && Array.isArray(option.children);
|
|
9
|
+
}
|
|
10
|
+
function normalizeOption(option, labelField, valueField, disabledField) {
|
|
11
|
+
const fields = option;
|
|
12
|
+
const value = fields[valueField];
|
|
13
|
+
const label = fields[labelField];
|
|
14
|
+
if (typeof value !== "string" && typeof value !== "number") throw new Error("ComboboxSelect options must have a string or number value.");
|
|
15
|
+
return {
|
|
16
|
+
value,
|
|
17
|
+
label: typeof label === "string" || typeof label === "number" ? String(label) : String(value),
|
|
18
|
+
disabled: Boolean(fields[disabledField]),
|
|
19
|
+
option
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function optionKey(value) {
|
|
23
|
+
return `${typeof value}:${value}`;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/components/combobox-select/combobox-select.tsx
|
|
27
|
+
const defaultMessages = {
|
|
28
|
+
clear: "Clear selection",
|
|
29
|
+
toggle: "Toggle options",
|
|
30
|
+
selected: "Selected options",
|
|
31
|
+
chipDescription: "Press Backspace or Delete to remove",
|
|
32
|
+
inputDescription: "From the start of the input, press Left Arrow to focus selected options",
|
|
33
|
+
remove: (label) => `Remove ${label}`
|
|
34
|
+
};
|
|
35
|
+
const defaultRenderCreateLabel = (query) => `Create “${query}”`;
|
|
36
|
+
function recordLabel(record, renderLabel) {
|
|
37
|
+
return record.option === void 0 ? record.label : renderLabel?.(record.option) ?? record.label;
|
|
38
|
+
}
|
|
39
|
+
function renderOptionContent({ record, selected, renderLabel, renderOption, renderCreateLabel }) {
|
|
40
|
+
if (record.createQuery !== void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Plus, { "aria-hidden": true }), /* @__PURE__ */ jsx("span", {
|
|
41
|
+
className: "r-combobox-select__text",
|
|
42
|
+
children: renderCreateLabel(record.label)
|
|
43
|
+
})] });
|
|
44
|
+
const content = record.option === void 0 ? record.label : renderOption?.(record.option, { selected }) ?? recordLabel(record, renderLabel);
|
|
45
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
46
|
+
className: "r-combobox-select__text",
|
|
47
|
+
children: content
|
|
48
|
+
}), /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
|
|
49
|
+
className: "r-combobox-select__indicator",
|
|
50
|
+
children: /* @__PURE__ */ jsx(Check, { "aria-hidden": true })
|
|
51
|
+
})] });
|
|
52
|
+
}
|
|
53
|
+
function renderSelectedChip({ record, index, tagLimit, strings, renderLabel, renderTag }) {
|
|
54
|
+
const label = recordLabel(record, renderLabel);
|
|
55
|
+
const content = record.option === void 0 ? record.label : renderTag?.(record.option) ?? label;
|
|
56
|
+
const overflow = index >= tagLimit;
|
|
57
|
+
return /* @__PURE__ */ jsxs(Combobox.Chip, {
|
|
58
|
+
className: "r-combobox-select__chip",
|
|
59
|
+
"data-overflow": overflow || void 0,
|
|
60
|
+
"aria-label": record.label,
|
|
61
|
+
"aria-description": strings.chipDescription,
|
|
62
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
63
|
+
className: "r-combobox-select__text",
|
|
64
|
+
children: content
|
|
65
|
+
}), /* @__PURE__ */ jsx(Combobox.ChipRemove, {
|
|
66
|
+
className: "r-combobox-select__remove",
|
|
67
|
+
"aria-label": strings.remove(record.label),
|
|
68
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
|
|
69
|
+
})]
|
|
70
|
+
}, optionKey(record.value));
|
|
71
|
+
}
|
|
72
|
+
/** Options-driven composition. The stable compound Combobox API stays unchanged. */
|
|
73
|
+
function isItemEqualToValue(a, b) {
|
|
74
|
+
return a.createQuery === b.createQuery && a.value === b.value;
|
|
75
|
+
}
|
|
76
|
+
function itemToStringLabel(record) {
|
|
77
|
+
return record.label;
|
|
78
|
+
}
|
|
79
|
+
function itemToStringValue(record) {
|
|
80
|
+
return String(record.value);
|
|
81
|
+
}
|
|
82
|
+
function ComboboxSelect({ options, multiple, value, defaultValue, onValueChange, labelField = "label", valueField = "value", disabledField = "disabled", fallbackOption, filterable = true, filter, remote = false, onSearch, loading = false, create = false, creatable, onCreate, clearable = false, showArrow = true, clearFilterAfterSelect = true, onClear, maxTagCount, size = "md", status, placeholder = "Select an option", renderLabel, renderOption, renderTag, renderCreateLabel = defaultRenderCreateLabel, emptyContent = "No options found", loadingContent = "Loading…", messages, inputProps, portalProps, positionerProps, popupProps, className, style, id: idProp, ref, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, inputValue, defaultInputValue, onInputValueChange, disabled, readOnly, autoHighlight = true, ...rootProps }) {
|
|
83
|
+
const generatedId = useId();
|
|
84
|
+
const id = idProp ?? generatedId;
|
|
85
|
+
const { resolvedMode } = useTheme();
|
|
86
|
+
const scopedPortalProps = useThemeScopeProps(portalProps ?? {});
|
|
87
|
+
const strings = {
|
|
88
|
+
...defaultMessages,
|
|
89
|
+
...messages
|
|
90
|
+
};
|
|
91
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? (multiple ? [] : null));
|
|
92
|
+
const selectedValue = value === void 0 ? uncontrolledValue : value;
|
|
93
|
+
const selectedKeys = useMemo(() => Array.isArray(selectedValue) ? selectedValue : selectedValue == null ? [] : [selectedValue], [selectedValue]);
|
|
94
|
+
const [query, setQuery] = useState(String(defaultInputValue ?? ""));
|
|
95
|
+
const [createdOptions, setCreatedOptions] = useState([]);
|
|
96
|
+
const collator = useMemo(() => new Intl.Collator(void 0, {
|
|
97
|
+
sensitivity: "base",
|
|
98
|
+
usage: "search"
|
|
99
|
+
}), []);
|
|
100
|
+
const normalized = useMemo(() => {
|
|
101
|
+
const normalize = (option) => normalizeOption(option, labelField, valueField, disabledField);
|
|
102
|
+
const groups = [];
|
|
103
|
+
options.forEach((option, index) => {
|
|
104
|
+
if (isOptionGroup(option)) groups.push({
|
|
105
|
+
key: `group:${optionKey(option.key)}`,
|
|
106
|
+
label: option.label,
|
|
107
|
+
items: option.children.map(normalize)
|
|
108
|
+
});
|
|
109
|
+
else groups.push({
|
|
110
|
+
key: `option:${index}`,
|
|
111
|
+
items: [normalize(option)]
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
const records = new Map(groups.flatMap((group) => group.items.map((record) => [record.value, record])));
|
|
115
|
+
const created = createdOptions.map(normalize).filter((record) => !records.has(record.value));
|
|
116
|
+
created.forEach((record) => records.set(record.value, record));
|
|
117
|
+
if (created.length) groups.push({
|
|
118
|
+
key: "created",
|
|
119
|
+
items: created
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
groups,
|
|
123
|
+
records
|
|
124
|
+
};
|
|
125
|
+
}, [
|
|
126
|
+
options,
|
|
127
|
+
createdOptions,
|
|
128
|
+
labelField,
|
|
129
|
+
valueField,
|
|
130
|
+
disabledField
|
|
131
|
+
]);
|
|
132
|
+
const [retained, setRetained] = useState(() => /* @__PURE__ */ new Map());
|
|
133
|
+
const selected = useMemo(() => {
|
|
134
|
+
return selectedKeys.map((key) => normalized.records.get(key) ?? retained.get(key) ?? (fallbackOption ? normalizeOption(fallbackOption(key), labelField, valueField, disabledField) : {
|
|
135
|
+
value: key,
|
|
136
|
+
label: String(key),
|
|
137
|
+
disabled: false
|
|
138
|
+
}));
|
|
139
|
+
}, [
|
|
140
|
+
selectedKeys,
|
|
141
|
+
normalized,
|
|
142
|
+
retained,
|
|
143
|
+
fallbackOption,
|
|
144
|
+
labelField,
|
|
145
|
+
valueField,
|
|
146
|
+
disabledField
|
|
147
|
+
]);
|
|
148
|
+
if (retained.size !== selected.length || !selected.every((record) => retained.get(record.value) === record)) setRetained(new Map(selected.map((record) => [record.value, record])));
|
|
149
|
+
const search = String(inputValue ?? query).trim();
|
|
150
|
+
const canCreate = (create || creatable) && filterable && !loading && search !== "" && ![...normalized.records.values(), ...selected].some((record) => record.label.trim().toLocaleLowerCase() === search.toLocaleLowerCase());
|
|
151
|
+
const items = useMemo(() => {
|
|
152
|
+
if (!canCreate) return normalized.groups;
|
|
153
|
+
const candidate = {
|
|
154
|
+
value: search,
|
|
155
|
+
label: search,
|
|
156
|
+
disabled: false,
|
|
157
|
+
createQuery: search
|
|
158
|
+
};
|
|
159
|
+
return [...normalized.groups, {
|
|
160
|
+
key: "create",
|
|
161
|
+
items: [candidate]
|
|
162
|
+
}];
|
|
163
|
+
}, [
|
|
164
|
+
canCreate,
|
|
165
|
+
normalized.groups,
|
|
166
|
+
search
|
|
167
|
+
]);
|
|
168
|
+
const css = `r-combobox-select r-combobox-select--${size}${resolvedMode === "dark" ? " r-combobox-select--dark" : ""}`;
|
|
169
|
+
const tagLimit = maxTagCount === void 0 ? Infinity : Math.max(0, Math.floor(maxTagCount));
|
|
170
|
+
const inputClassName = ["r-combobox-select__input", typeof inputProps?.className === "string" ? inputProps.className : null].filter(Boolean).join(" ");
|
|
171
|
+
const controlClassName = [
|
|
172
|
+
css,
|
|
173
|
+
"r-combobox-select__control",
|
|
174
|
+
className
|
|
175
|
+
].filter(Boolean).join(" ");
|
|
176
|
+
const positionerClassName = ["r-combobox-select__positioner", typeof positionerProps?.className === "string" ? positionerProps.className : null].filter(Boolean).join(" ");
|
|
177
|
+
const popupClassName = [
|
|
178
|
+
css,
|
|
179
|
+
"r-combobox-select__popup",
|
|
180
|
+
typeof popupProps?.className === "string" ? popupProps.className : null
|
|
181
|
+
].filter(Boolean).join(" ");
|
|
182
|
+
const inputRef = ref ?? inputProps?.ref;
|
|
183
|
+
const inputLabel = ariaLabel ?? inputProps?.["aria-label"];
|
|
184
|
+
const inputLabelledBy = ariaLabelledBy ?? inputProps?.["aria-labelledby"];
|
|
185
|
+
const inputDescribedBy = ariaDescribedBy ?? inputProps?.["aria-describedby"];
|
|
186
|
+
const inputInvalid = ariaInvalid ?? (status === "error" ? true : inputProps?.["aria-invalid"]);
|
|
187
|
+
const inputDescription = multiple && selected.length ? strings.inputDescription : inputProps?.["aria-description"];
|
|
188
|
+
const inputPlaceholder = multiple && selected.length ? "" : placeholder;
|
|
189
|
+
const inputReadOnly = readOnly || !filterable;
|
|
190
|
+
const controlValue = multiple ? selected : selected[0] ?? null;
|
|
191
|
+
const handleValueChange = useCallback((next, details) => {
|
|
192
|
+
const nextRecords = next === null ? [] : Array.isArray(next) ? next : [next];
|
|
193
|
+
const createRecord = nextRecords.find((record) => record.createQuery !== void 0);
|
|
194
|
+
let newOption;
|
|
195
|
+
let resolved = nextRecords;
|
|
196
|
+
if (createRecord) {
|
|
197
|
+
const created = onCreate ? onCreate(createRecord.label) : {
|
|
198
|
+
[labelField]: createRecord.label,
|
|
199
|
+
[valueField]: createRecord.label
|
|
200
|
+
};
|
|
201
|
+
if (created === null) {
|
|
202
|
+
details.cancel();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const record = normalizeOption(created, labelField, valueField, disabledField);
|
|
206
|
+
if (record.disabled) {
|
|
207
|
+
details.cancel();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
newOption = created;
|
|
211
|
+
const existing = normalized.records.get(record.value) ?? retained.get(record.value);
|
|
212
|
+
resolved = nextRecords.map((item) => item === createRecord ? existing ?? record : item);
|
|
213
|
+
resolved = [...new Map(resolved.map((item) => [item.value, item])).values()];
|
|
214
|
+
}
|
|
215
|
+
const nextValue = multiple ? resolved.map((record) => record.value) : resolved[0]?.value ?? null;
|
|
216
|
+
const nextOptions = multiple ? resolved.map((record) => record.option) : resolved[0]?.option ?? null;
|
|
217
|
+
onValueChange?.(nextValue, nextOptions, details);
|
|
218
|
+
if (details.isCanceled) return;
|
|
219
|
+
if (newOption !== void 0) setCreatedOptions((previous) => [...previous, newOption]);
|
|
220
|
+
setRetained(new Map(resolved.map((record) => [record.value, record])));
|
|
221
|
+
if (value === void 0) setUncontrolledValue(nextValue);
|
|
222
|
+
if (details.reason === "clear-press") onClear?.();
|
|
223
|
+
}, [
|
|
224
|
+
multiple,
|
|
225
|
+
onCreate,
|
|
226
|
+
labelField,
|
|
227
|
+
valueField,
|
|
228
|
+
disabledField,
|
|
229
|
+
normalized.records,
|
|
230
|
+
retained,
|
|
231
|
+
onValueChange,
|
|
232
|
+
value,
|
|
233
|
+
onClear
|
|
234
|
+
]);
|
|
235
|
+
const handleInputValueChange = useCallback((next, details) => {
|
|
236
|
+
onInputValueChange?.(next, details);
|
|
237
|
+
if (details.isCanceled) return;
|
|
238
|
+
const shouldKeepQuery = details.reason === "input-change" || details.reason === "item-press" && !clearFilterAfterSelect;
|
|
239
|
+
setQuery(shouldKeepQuery ? next : "");
|
|
240
|
+
if (details.reason === "input-change" || details.reason === "clear-press") onSearch?.(next);
|
|
241
|
+
}, [
|
|
242
|
+
onInputValueChange,
|
|
243
|
+
clearFilterAfterSelect,
|
|
244
|
+
onSearch
|
|
245
|
+
]);
|
|
246
|
+
const filterOptions = useCallback((record, pattern) => {
|
|
247
|
+
if (record.createQuery !== void 0) return true;
|
|
248
|
+
if (remote || !filterable || filter === false) return true;
|
|
249
|
+
return record.option !== void 0 && typeof filter === "function" ? filter(pattern, record.option) : collator.compare(record.label.slice(0, pattern.length), pattern) === 0 || record.label.toLocaleLowerCase().includes(pattern.toLocaleLowerCase());
|
|
250
|
+
}, [
|
|
251
|
+
remote,
|
|
252
|
+
filterable,
|
|
253
|
+
filter,
|
|
254
|
+
collator
|
|
255
|
+
]);
|
|
256
|
+
const selectedChips = selected.map((record, index) => renderSelectedChip({
|
|
257
|
+
record,
|
|
258
|
+
index,
|
|
259
|
+
tagLimit,
|
|
260
|
+
strings,
|
|
261
|
+
renderLabel,
|
|
262
|
+
renderTag
|
|
263
|
+
}));
|
|
264
|
+
const overflowCount = selected.length > tagLimit ? selected.length - tagLimit : 0;
|
|
265
|
+
const overflowTitle = selected.slice(tagLimit).map((record) => record.label).join(", ");
|
|
266
|
+
const overflow = overflowCount ? /* @__PURE__ */ jsxs("span", {
|
|
267
|
+
className: "r-combobox-select__overflow",
|
|
268
|
+
title: overflowTitle,
|
|
269
|
+
children: ["+", overflowCount]
|
|
270
|
+
}) : null;
|
|
271
|
+
function renderRecord(record) {
|
|
272
|
+
const itemKey = record.createQuery !== void 0 ? "create" : optionKey(record.value);
|
|
273
|
+
const content = renderOptionContent({
|
|
274
|
+
record,
|
|
275
|
+
selected: selected.some((item) => item.value === record.value),
|
|
276
|
+
renderLabel,
|
|
277
|
+
renderOption,
|
|
278
|
+
renderCreateLabel
|
|
279
|
+
});
|
|
280
|
+
return /* @__PURE__ */ jsx(Combobox.Item, {
|
|
281
|
+
value: record,
|
|
282
|
+
disabled: record.disabled || loading,
|
|
283
|
+
className: "r-combobox-select__option",
|
|
284
|
+
children: content
|
|
285
|
+
}, itemKey);
|
|
286
|
+
}
|
|
287
|
+
function renderGroup(group) {
|
|
288
|
+
const groupLabel = group.label == null ? null : /* @__PURE__ */ jsx(Combobox.GroupLabel, {
|
|
289
|
+
className: "r-combobox-select__group-label",
|
|
290
|
+
children: group.label
|
|
291
|
+
});
|
|
292
|
+
return /* @__PURE__ */ jsxs(Combobox.Group, {
|
|
293
|
+
items: group.items,
|
|
294
|
+
children: [groupLabel, /* @__PURE__ */ jsx(Combobox.Collection, { children: renderRecord })]
|
|
295
|
+
}, group.key);
|
|
296
|
+
}
|
|
297
|
+
const input = /* @__PURE__ */ jsx(Combobox.Input, {
|
|
298
|
+
...inputProps,
|
|
299
|
+
ref: inputRef,
|
|
300
|
+
id,
|
|
301
|
+
"data-slot": "combobox-select-input",
|
|
302
|
+
className: inputClassName,
|
|
303
|
+
"aria-label": inputLabel,
|
|
304
|
+
"aria-labelledby": inputLabelledBy,
|
|
305
|
+
"aria-describedby": inputDescribedBy,
|
|
306
|
+
"aria-invalid": inputInvalid,
|
|
307
|
+
"aria-description": inputDescription,
|
|
308
|
+
placeholder: inputPlaceholder,
|
|
309
|
+
readOnly: inputReadOnly
|
|
310
|
+
});
|
|
311
|
+
const controlContent = multiple ? /* @__PURE__ */ jsxs(Combobox.Chips, {
|
|
312
|
+
className: "r-combobox-select__chips",
|
|
313
|
+
"aria-label": strings.selected,
|
|
314
|
+
children: [
|
|
315
|
+
selectedChips,
|
|
316
|
+
overflow,
|
|
317
|
+
input
|
|
318
|
+
]
|
|
319
|
+
}) : input;
|
|
320
|
+
const loadingMessage = loading ? /* @__PURE__ */ jsx("div", {
|
|
321
|
+
className: "r-combobox-select__message",
|
|
322
|
+
children: loadingContent
|
|
323
|
+
}) : null;
|
|
324
|
+
const emptyMessage = !loading ? /* @__PURE__ */ jsx("div", {
|
|
325
|
+
className: "r-combobox-select__message",
|
|
326
|
+
children: emptyContent
|
|
327
|
+
}) : null;
|
|
328
|
+
const loadingIndicator = loading ? /* @__PURE__ */ jsx(LoaderCircle, {
|
|
329
|
+
className: "r-combobox-select__spinner",
|
|
330
|
+
"aria-hidden": true
|
|
331
|
+
}) : null;
|
|
332
|
+
const clearAction = clearable ? /* @__PURE__ */ jsx(Combobox.Clear, {
|
|
333
|
+
className: "r-combobox-select__action",
|
|
334
|
+
"aria-label": strings.clear,
|
|
335
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
|
|
336
|
+
}) : null;
|
|
337
|
+
const triggerAction = showArrow ? /* @__PURE__ */ jsx(Combobox.Trigger, {
|
|
338
|
+
className: "r-combobox-select__action",
|
|
339
|
+
"aria-label": strings.toggle,
|
|
340
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true })
|
|
341
|
+
}) : null;
|
|
342
|
+
const actions = /* @__PURE__ */ jsxs("span", {
|
|
343
|
+
className: "r-combobox-select__actions",
|
|
344
|
+
children: [
|
|
345
|
+
loadingIndicator,
|
|
346
|
+
clearAction,
|
|
347
|
+
triggerAction
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
const valueChange = handleValueChange;
|
|
351
|
+
return /* @__PURE__ */ jsxs(Combobox.Root, {
|
|
352
|
+
...rootProps,
|
|
353
|
+
id,
|
|
354
|
+
multiple,
|
|
355
|
+
disabled,
|
|
356
|
+
readOnly,
|
|
357
|
+
items,
|
|
358
|
+
value: controlValue,
|
|
359
|
+
onValueChange: valueChange,
|
|
360
|
+
inputValue,
|
|
361
|
+
defaultInputValue,
|
|
362
|
+
onInputValueChange: handleInputValueChange,
|
|
363
|
+
filter: filterOptions,
|
|
364
|
+
itemToStringLabel,
|
|
365
|
+
itemToStringValue,
|
|
366
|
+
isItemEqualToValue,
|
|
367
|
+
autoHighlight,
|
|
368
|
+
children: [/* @__PURE__ */ jsxs(Combobox.InputGroup, {
|
|
369
|
+
"data-slot": "combobox-select",
|
|
370
|
+
"data-status": status,
|
|
371
|
+
className: controlClassName,
|
|
372
|
+
style,
|
|
373
|
+
children: [controlContent, actions]
|
|
374
|
+
}), /* @__PURE__ */ jsx(Combobox.Portal, {
|
|
375
|
+
...scopedPortalProps,
|
|
376
|
+
children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
377
|
+
sideOffset: 4,
|
|
378
|
+
align: "start",
|
|
379
|
+
...positionerProps,
|
|
380
|
+
className: positionerClassName,
|
|
381
|
+
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
382
|
+
initialFocus: false,
|
|
383
|
+
finalFocus: false,
|
|
384
|
+
...popupProps,
|
|
385
|
+
"data-slot": "combobox-select-popup",
|
|
386
|
+
className: popupClassName,
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ jsx(Combobox.Status, { children: loadingMessage }),
|
|
389
|
+
/* @__PURE__ */ jsx(Combobox.List, {
|
|
390
|
+
className: "r-combobox-select__list",
|
|
391
|
+
"aria-busy": loading,
|
|
392
|
+
children: renderGroup
|
|
393
|
+
}),
|
|
394
|
+
/* @__PURE__ */ jsx(Combobox.Empty, { children: emptyMessage })
|
|
395
|
+
]
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
})]
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/components/announcement-dialog/announcement-dialog.tsx
|
|
403
|
+
const announcement = tv({ slots: {
|
|
404
|
+
content: ["@container/announcement max-w-xl gap-0 overflow-clip p-0"],
|
|
405
|
+
body: [
|
|
406
|
+
"min-h-0 max-h-[70dvh] overflow-y-auto pb-5",
|
|
407
|
+
"[&_[data-slot=announcement-picture]+[data-slot=announcement-text]]:mt-5",
|
|
408
|
+
"px-5 pt-5"
|
|
409
|
+
],
|
|
410
|
+
header: ["flex min-w-0 items-center justify-between gap-3"],
|
|
411
|
+
label: ["flex min-w-0 items-center gap-2 wrap-break-word", "text-caption text-foreground-muted"],
|
|
412
|
+
dot: ["shrink-0", "size-[9px] rounded-[2px] border-[1.5px] border-sticker-ink bg-attention-400"],
|
|
413
|
+
title: ["mt-3 wrap-break-word normal-case", "text-headline font-bold tracking-normal"],
|
|
414
|
+
picture: ["mt-5 h-44 w-full overflow-hidden border border-line-hairline [&_img]:size-full [&_img]:object-contain", "rounded-container bg-fill-muted"],
|
|
415
|
+
text: [
|
|
416
|
+
"mt-3 min-w-0 wrap-break-word [&_p+p]:mt-3",
|
|
417
|
+
"[&_ul]:list-disc [&_ol]:list-decimal [&_ul]:pl-5 [&_ol]:pl-5 [&_h1]:text-xl [&_h2]:text-lg [&_h3]:text-base [&_h1]:font-bold [&_h2]:font-bold [&_h3]:font-bold [&_pre]:max-w-full [&_pre]:overflow-x-auto",
|
|
418
|
+
"text-subhead text-foreground-muted"
|
|
419
|
+
],
|
|
420
|
+
footer: ["flex shrink-0 flex-wrap items-center justify-between gap-3 border-line-hairline py-4 @max-[28rem]/announcement:flex-col", "border-t-0 px-5 pt-0 pb-5"],
|
|
421
|
+
action: ["ml-auto h-auto min-h-8 min-w-0 max-w-full py-1 whitespace-normal text-center font-bold @max-[28rem]/announcement:ml-0 @max-[28rem]/announcement:w-full"],
|
|
422
|
+
secondary: ["min-w-0 max-w-full px-0 whitespace-normal underline @max-[28rem]/announcement:w-full"]
|
|
423
|
+
} });
|
|
424
|
+
function AnnouncementDialogContent({ className, initialFocus, ref, ...props }) {
|
|
425
|
+
const localRef = useRef(null);
|
|
426
|
+
const setRef = useCallback((node) => {
|
|
427
|
+
localRef.current = node;
|
|
428
|
+
if (typeof ref === "function") return ref(node);
|
|
429
|
+
if (ref) ref.current = node;
|
|
430
|
+
}, [ref]);
|
|
431
|
+
const focusAction = useCallback(() => localRef.current?.querySelector("[data-announcement-action]:not(:disabled)") ?? true, []);
|
|
432
|
+
const { content } = announcement();
|
|
433
|
+
return /* @__PURE__ */ jsx(DialogContent, {
|
|
434
|
+
ref: setRef,
|
|
435
|
+
initialFocus: initialFocus ?? focusAction,
|
|
436
|
+
className: content({ className }),
|
|
437
|
+
...props
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function AnnouncementDialogBody({ className, ...props }) {
|
|
441
|
+
const { body } = announcement();
|
|
442
|
+
return /* @__PURE__ */ jsx("div", {
|
|
443
|
+
"data-slot": "announcement-body",
|
|
444
|
+
className: body({ className }),
|
|
445
|
+
...props
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function AnnouncementDialogHeader({ className, ...props }) {
|
|
449
|
+
const { header } = announcement();
|
|
450
|
+
return /* @__PURE__ */ jsx("div", {
|
|
451
|
+
"data-slot": "announcement-header",
|
|
452
|
+
className: header({ className }),
|
|
453
|
+
...props
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function AnnouncementDialogLabel({ className, children, ...props }) {
|
|
457
|
+
const { label, dot } = announcement();
|
|
458
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
459
|
+
"data-slot": "announcement-label",
|
|
460
|
+
className: label({ className }),
|
|
461
|
+
...props,
|
|
462
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
463
|
+
"aria-hidden": "true",
|
|
464
|
+
className: dot()
|
|
465
|
+
}), children]
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
function AnnouncementDialogTitle({ className, ...props }) {
|
|
469
|
+
const { title } = announcement();
|
|
470
|
+
return /* @__PURE__ */ jsx(DialogTitle, {
|
|
471
|
+
className: title({ className }),
|
|
472
|
+
...props
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function AnnouncementDialogPicture({ className, ...props }) {
|
|
476
|
+
const { picture } = announcement();
|
|
477
|
+
return /* @__PURE__ */ jsx("div", {
|
|
478
|
+
"data-slot": "announcement-picture",
|
|
479
|
+
className: picture({ className }),
|
|
480
|
+
...props
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function AnnouncementDialogText({ className, ...props }) {
|
|
484
|
+
const { text } = announcement();
|
|
485
|
+
return /* @__PURE__ */ jsx("div", {
|
|
486
|
+
"data-slot": "announcement-text",
|
|
487
|
+
className: text({ className }),
|
|
488
|
+
...props
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
function AnnouncementDialogFooter({ className, ...props }) {
|
|
492
|
+
const { footer } = announcement();
|
|
493
|
+
return /* @__PURE__ */ jsx("div", {
|
|
494
|
+
"data-slot": "announcement-footer",
|
|
495
|
+
className: footer({ className }),
|
|
496
|
+
...props
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function AnnouncementDialogAction({ className, ...props }) {
|
|
500
|
+
const { action } = announcement();
|
|
501
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
502
|
+
variant: "accent",
|
|
503
|
+
"data-announcement-action": true,
|
|
504
|
+
className: action({ className }),
|
|
505
|
+
...props
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
function AnnouncementDialogSecondaryAction({ className, ...props }) {
|
|
509
|
+
const { secondary } = announcement();
|
|
510
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
511
|
+
variant: "link",
|
|
512
|
+
className: secondary({ className }),
|
|
513
|
+
...props
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
517
|
+
export { Dialog as AnnouncementDialog, AnnouncementDialogAction, AnnouncementDialogBody, DialogClose as AnnouncementDialogClose, AnnouncementDialogContent, AnnouncementDialogFooter, AnnouncementDialogHeader, AnnouncementDialogLabel, AnnouncementDialogPicture, AnnouncementDialogSecondaryAction, AnnouncementDialogText, AnnouncementDialogTitle, DialogTrigger as AnnouncementDialogTrigger, ComboboxSelect };
|