@fanvue/ui 1.17.2 → 1.18.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.
Files changed (79) hide show
  1. package/dist/cjs/components/Accordion/Accordion.cjs +29 -0
  2. package/dist/cjs/components/Accordion/Accordion.cjs.map +1 -0
  3. package/dist/cjs/components/Accordion/AccordionContent.cjs +44 -0
  4. package/dist/cjs/components/Accordion/AccordionContent.cjs.map +1 -0
  5. package/dist/cjs/components/Accordion/AccordionItem.cjs +40 -0
  6. package/dist/cjs/components/Accordion/AccordionItem.cjs.map +1 -0
  7. package/dist/cjs/components/Accordion/AccordionTrigger.cjs +55 -0
  8. package/dist/cjs/components/Accordion/AccordionTrigger.cjs.map +1 -0
  9. package/dist/cjs/components/Autocomplete/Autocomplete.cjs +239 -0
  10. package/dist/cjs/components/Autocomplete/Autocomplete.cjs.map +1 -0
  11. package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs +52 -0
  12. package/dist/cjs/components/Autocomplete/AutocompleteDropdownContent.cjs.map +1 -0
  13. package/dist/cjs/components/Autocomplete/AutocompleteOptionItem.cjs +53 -0
  14. package/dist/cjs/components/Autocomplete/AutocompleteOptionItem.cjs.map +1 -0
  15. package/dist/cjs/components/Autocomplete/AutocompleteTag.cjs +36 -0
  16. package/dist/cjs/components/Autocomplete/AutocompleteTag.cjs.map +1 -0
  17. package/dist/cjs/components/Autocomplete/constants.cjs +15 -0
  18. package/dist/cjs/components/Autocomplete/constants.cjs.map +1 -0
  19. package/dist/cjs/components/Autocomplete/useAutocomplete.cjs +284 -0
  20. package/dist/cjs/components/Autocomplete/useAutocomplete.cjs.map +1 -0
  21. package/dist/cjs/components/Avatar/Avatar.cjs +7 -7
  22. package/dist/cjs/components/Avatar/Avatar.cjs.map +1 -1
  23. package/dist/cjs/components/BottomNavigation/BottomNavigation.cjs +68 -0
  24. package/dist/cjs/components/BottomNavigation/BottomNavigation.cjs.map +1 -0
  25. package/dist/cjs/components/BottomNavigation/BottomNavigationAction.cjs +79 -0
  26. package/dist/cjs/components/BottomNavigation/BottomNavigationAction.cjs.map +1 -0
  27. package/dist/cjs/components/Chip/Chip.cjs +1 -8
  28. package/dist/cjs/components/Chip/Chip.cjs.map +1 -1
  29. package/dist/cjs/components/Dialog/Dialog.cjs +170 -0
  30. package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -0
  31. package/dist/cjs/components/Drawer/Drawer.cjs +151 -0
  32. package/dist/cjs/components/Drawer/Drawer.cjs.map +1 -0
  33. package/dist/cjs/components/MobileStepper/MobileStepper.cjs +136 -0
  34. package/dist/cjs/components/MobileStepper/MobileStepper.cjs.map +1 -0
  35. package/dist/cjs/components/ProgressBar/ProgressBar.cjs +2 -0
  36. package/dist/cjs/components/ProgressBar/ProgressBar.cjs.map +1 -1
  37. package/dist/cjs/index.cjs +37 -0
  38. package/dist/cjs/index.cjs.map +1 -1
  39. package/dist/components/Accordion/Accordion.mjs +11 -0
  40. package/dist/components/Accordion/Accordion.mjs.map +1 -0
  41. package/dist/components/Accordion/AccordionContent.mjs +26 -0
  42. package/dist/components/Accordion/AccordionContent.mjs.map +1 -0
  43. package/dist/components/Accordion/AccordionItem.mjs +22 -0
  44. package/dist/components/Accordion/AccordionItem.mjs.map +1 -0
  45. package/dist/components/Accordion/AccordionTrigger.mjs +37 -0
  46. package/dist/components/Accordion/AccordionTrigger.mjs.map +1 -0
  47. package/dist/components/Autocomplete/Autocomplete.mjs +221 -0
  48. package/dist/components/Autocomplete/Autocomplete.mjs.map +1 -0
  49. package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs +52 -0
  50. package/dist/components/Autocomplete/AutocompleteDropdownContent.mjs.map +1 -0
  51. package/dist/components/Autocomplete/AutocompleteOptionItem.mjs +53 -0
  52. package/dist/components/Autocomplete/AutocompleteOptionItem.mjs.map +1 -0
  53. package/dist/components/Autocomplete/AutocompleteTag.mjs +36 -0
  54. package/dist/components/Autocomplete/AutocompleteTag.mjs.map +1 -0
  55. package/dist/components/Autocomplete/constants.mjs +15 -0
  56. package/dist/components/Autocomplete/constants.mjs.map +1 -0
  57. package/dist/components/Autocomplete/useAutocomplete.mjs +267 -0
  58. package/dist/components/Autocomplete/useAutocomplete.mjs.map +1 -0
  59. package/dist/components/Avatar/Avatar.mjs +7 -7
  60. package/dist/components/Avatar/Avatar.mjs.map +1 -1
  61. package/dist/components/BottomNavigation/BottomNavigation.mjs +51 -0
  62. package/dist/components/BottomNavigation/BottomNavigation.mjs.map +1 -0
  63. package/dist/components/BottomNavigation/BottomNavigationAction.mjs +62 -0
  64. package/dist/components/BottomNavigation/BottomNavigationAction.mjs.map +1 -0
  65. package/dist/components/Chip/Chip.mjs +1 -8
  66. package/dist/components/Chip/Chip.mjs.map +1 -1
  67. package/dist/components/Dialog/Dialog.mjs +152 -0
  68. package/dist/components/Dialog/Dialog.mjs.map +1 -0
  69. package/dist/components/Drawer/Drawer.mjs +133 -0
  70. package/dist/components/Drawer/Drawer.mjs.map +1 -0
  71. package/dist/components/MobileStepper/MobileStepper.mjs +119 -0
  72. package/dist/components/MobileStepper/MobileStepper.mjs.map +1 -0
  73. package/dist/components/ProgressBar/ProgressBar.mjs +2 -0
  74. package/dist/components/ProgressBar/ProgressBar.mjs.map +1 -1
  75. package/dist/index.d.ts +510 -0
  76. package/dist/index.mjs +37 -0
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/styles/theme.css +26 -0
  79. package/package.json +5 -2
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import { cn } from "../../utils/cn.mjs";
4
+ import { CheckIcon } from "../Icons/CheckIcon.mjs";
5
+ import { CREATE_PREFIX, getLabel } from "./constants.mjs";
6
+ function AutocompleteOptionItem({
7
+ option,
8
+ optionId,
9
+ index,
10
+ isSelected,
11
+ isActive,
12
+ onSelect,
13
+ onMouseEnter,
14
+ renderOption
15
+ }) {
16
+ const isCreate = option.value.startsWith(CREATE_PREFIX);
17
+ return /* @__PURE__ */ jsx(
18
+ "div",
19
+ {
20
+ id: optionId,
21
+ role: "option",
22
+ tabIndex: -1,
23
+ "aria-selected": isSelected,
24
+ "aria-disabled": option.disabled || void 0,
25
+ "data-option-index": index,
26
+ className: cn(
27
+ "typography-regular-body-lg relative flex w-full cursor-pointer select-none items-center gap-2 rounded-lg py-2 pr-2 pl-3 text-foreground-default outline-none",
28
+ isActive && "bg-neutral-100",
29
+ option.disabled && "pointer-events-none opacity-50",
30
+ isCreate && "italic"
31
+ ),
32
+ onMouseEnter,
33
+ onMouseDown: (e) => e.preventDefault(),
34
+ onClick: () => {
35
+ if (!option.disabled) onSelect();
36
+ },
37
+ onKeyDown: (e) => {
38
+ if (e.key === "Enter" || e.key === " ") {
39
+ e.preventDefault();
40
+ if (!option.disabled) onSelect();
41
+ }
42
+ },
43
+ children: renderOption ? renderOption(option, { selected: isSelected, active: isActive }) : /* @__PURE__ */ jsxs(Fragment, { children: [
44
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: getLabel(option) }),
45
+ isSelected && /* @__PURE__ */ jsx("span", { className: "ml-auto flex size-4 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4 text-foreground-default" }) })
46
+ ] })
47
+ }
48
+ );
49
+ }
50
+ export {
51
+ AutocompleteOptionItem
52
+ };
53
+ //# sourceMappingURL=AutocompleteOptionItem.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AutocompleteOptionItem.mjs","sources":["../../../src/components/Autocomplete/AutocompleteOptionItem.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { CheckIcon } from \"../Icons/CheckIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { CREATE_PREFIX, getLabel } from \"./constants\";\n\nexport function AutocompleteOptionItem({\n option,\n optionId,\n index,\n isSelected,\n isActive,\n onSelect,\n onMouseEnter,\n renderOption,\n}: {\n option: AutocompleteOption;\n optionId: string;\n index: number;\n isSelected: boolean;\n isActive: boolean;\n onSelect: () => void;\n onMouseEnter: () => void;\n renderOption?: (\n option: AutocompleteOption,\n state: { selected: boolean; active: boolean },\n ) => React.ReactNode;\n}) {\n const isCreate = option.value.startsWith(CREATE_PREFIX);\n\n return (\n <div\n id={optionId}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={option.disabled || undefined}\n data-option-index={index}\n className={cn(\n \"typography-regular-body-lg relative flex w-full cursor-pointer select-none items-center gap-2 rounded-lg py-2 pr-2 pl-3 text-foreground-default outline-none\",\n isActive && \"bg-neutral-100\",\n option.disabled && \"pointer-events-none opacity-50\",\n isCreate && \"italic\",\n )}\n onMouseEnter={onMouseEnter}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (!option.disabled) onSelect();\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n if (!option.disabled) onSelect();\n }\n }}\n >\n {renderOption ? (\n renderOption(option, { selected: isSelected, active: isActive })\n ) : (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{getLabel(option)}</span>\n {isSelected && (\n <span className=\"ml-auto flex size-4 shrink-0 items-center justify-center\">\n <CheckIcon className=\"size-4 text-foreground-default\" />\n </span>\n )}\n </>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;AAMO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAYG;AACD,QAAM,WAAW,OAAO,MAAM,WAAW,aAAa;AAEtD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ,MAAK;AAAA,MACL,UAAU;AAAA,MACV,iBAAe;AAAA,MACf,iBAAe,OAAO,YAAY;AAAA,MAClC,qBAAmB;AAAA,MACnB,WAAW;AAAA,QACT;AAAA,QACA,YAAY;AAAA,QACZ,OAAO,YAAY;AAAA,QACnB,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA,aAAa,CAAC,MAAM,EAAE,eAAA;AAAA,MACtB,SAAS,MAAM;AACb,YAAI,CAAC,OAAO,SAAU,UAAA;AAAA,MACxB;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAA;AACF,cAAI,CAAC,OAAO,SAAU,UAAA;AAAA,QACxB;AAAA,MACF;AAAA,MAEC,UAAA,eACC,aAAa,QAAQ,EAAE,UAAU,YAAY,QAAQ,SAAA,CAAU,IAE/D,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,2BAA2B,UAAA,SAAS,MAAM,GAAE;AAAA,QAC3D,kCACE,QAAA,EAAK,WAAU,4DACd,UAAA,oBAAC,WAAA,EAAU,WAAU,iCAAA,CAAiC,EAAA,CACxD;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA;AAIR;"}
@@ -0,0 +1,36 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { CloseIcon } from "../Icons/CloseIcon.mjs";
4
+ import { getLabel } from "./constants.mjs";
5
+ function AutocompleteTag({
6
+ option,
7
+ disabled,
8
+ onRemove,
9
+ renderTag
10
+ }) {
11
+ if (renderTag) {
12
+ return /* @__PURE__ */ jsx("span", { children: renderTag(option, onRemove) });
13
+ }
14
+ return /* @__PURE__ */ jsxs("span", { className: "typography-regular-body-sm inline-flex max-w-full items-center gap-1 rounded-md bg-neutral-200 px-2 py-0.5 text-foreground-default", children: [
15
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: getLabel(option) }),
16
+ /* @__PURE__ */ jsx(
17
+ "button",
18
+ {
19
+ type: "button",
20
+ tabIndex: -1,
21
+ "aria-label": `Remove ${getLabel(option)}`,
22
+ className: "flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-foreground-secondary hover:text-foreground-default active:scale-95",
23
+ onClick: (e) => {
24
+ e.stopPropagation();
25
+ onRemove();
26
+ },
27
+ disabled,
28
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "size-3" })
29
+ }
30
+ )
31
+ ] });
32
+ }
33
+ export {
34
+ AutocompleteTag
35
+ };
36
+ //# sourceMappingURL=AutocompleteTag.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AutocompleteTag.mjs","sources":["../../../src/components/Autocomplete/AutocompleteTag.tsx"],"sourcesContent":["import type * as React from \"react\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport type { AutocompleteOption } from \"./Autocomplete\";\nimport { getLabel } from \"./constants\";\n\nexport function AutocompleteTag({\n option,\n disabled,\n onRemove,\n renderTag,\n}: {\n option: AutocompleteOption;\n disabled: boolean;\n onRemove: () => void;\n renderTag?: (option: AutocompleteOption, onRemove: () => void) => React.ReactNode;\n}) {\n if (renderTag) {\n return <span>{renderTag(option, onRemove)}</span>;\n }\n\n return (\n <span className=\"typography-regular-body-sm inline-flex max-w-full items-center gap-1 rounded-md bg-neutral-200 px-2 py-0.5 text-foreground-default\">\n <span className=\"truncate\">{getLabel(option)}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={`Remove ${getLabel(option)}`}\n className=\"flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm text-foreground-secondary hover:text-foreground-default active:scale-95\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n disabled={disabled}\n >\n <CloseIcon className=\"size-3\" />\n </button>\n </span>\n );\n}\n"],"names":[],"mappings":";;;;AAKO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,MAAI,WAAW;AACb,WAAO,oBAAC,QAAA,EAAM,UAAA,UAAU,QAAQ,QAAQ,GAAE;AAAA,EAC5C;AAEA,SACE,qBAAC,QAAA,EAAK,WAAU,sIACd,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,YAAY,UAAA,SAAS,MAAM,GAAE;AAAA,IAC7C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,UAAU;AAAA,QACV,cAAY,UAAU,SAAS,MAAM,CAAC;AAAA,QACtC,WAAU;AAAA,QACV,SAAS,CAAC,MAAM;AACd,YAAE,gBAAA;AACF,mBAAA;AAAA,QACF;AAAA,QACA;AAAA,QAEA,UAAA,oBAAC,WAAA,EAAU,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAChC,GACF;AAEJ;"}
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ const CREATE_PREFIX = "__create__";
3
+ function defaultFilter(option, query) {
4
+ const label = option.label ?? option.value;
5
+ return label.toLowerCase().includes(query.toLowerCase());
6
+ }
7
+ function getLabel(option) {
8
+ return option.label ?? option.value;
9
+ }
10
+ export {
11
+ CREATE_PREFIX,
12
+ defaultFilter,
13
+ getLabel
14
+ };
15
+ //# sourceMappingURL=constants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.mjs","sources":["../../../src/components/Autocomplete/constants.ts"],"sourcesContent":["import type { AutocompleteOption } from \"./Autocomplete\";\n\nexport const CREATE_PREFIX = \"__create__\";\n\nexport function defaultFilter(option: AutocompleteOption, query: string): boolean {\n const label = option.label ?? option.value;\n return label.toLowerCase().includes(query.toLowerCase());\n}\n\nexport function getLabel(option: AutocompleteOption): string {\n return option.label ?? option.value;\n}\n"],"names":[],"mappings":";AAEO,MAAM,gBAAgB;AAEtB,SAAS,cAAc,QAA4B,OAAwB;AAChF,QAAM,QAAQ,OAAO,SAAS,OAAO;AACrC,SAAO,MAAM,YAAA,EAAc,SAAS,MAAM,aAAa;AACzD;AAEO,SAAS,SAAS,QAAoC;AAC3D,SAAO,OAAO,SAAS,OAAO;AAChC;"}
@@ -0,0 +1,267 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { defaultFilter, CREATE_PREFIX, getLabel } from "./constants.mjs";
4
+ function useAutocomplete(props) {
5
+ const {
6
+ id,
7
+ options,
8
+ disabled = false,
9
+ filterFn,
10
+ creatable = false,
11
+ creatableLabel,
12
+ onCreate,
13
+ inputValue: inputValueProp,
14
+ onInputChange,
15
+ open: openProp,
16
+ defaultOpen = false,
17
+ onOpenChange
18
+ } = props;
19
+ const generatedId = React.useId();
20
+ const inputId = id ?? generatedId;
21
+ const helperTextId = `${inputId}-helper`;
22
+ const listboxId = `${inputId}-listbox`;
23
+ const inputRef = React.useRef(null);
24
+ const listRef = React.useRef(null);
25
+ const isMulti = props.multiple === true;
26
+ const [internalValue, setInternalValue] = React.useState(
27
+ !isMulti && props.defaultValue || null
28
+ );
29
+ const selectedValue = !isMulti ? props.value !== void 0 ? props.value : internalValue : null;
30
+ const [internalMultiValue, setInternalMultiValue] = React.useState(
31
+ isMulti && props.defaultValue ? props.defaultValue : []
32
+ );
33
+ const selectedMultiValues = isMulti ? props.value !== void 0 ? props.value : internalMultiValue : [];
34
+ const [internalInputValue, setInternalInputValue] = React.useState("");
35
+ const searchText = inputValueProp !== void 0 ? inputValueProp : internalInputValue;
36
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
37
+ const isOpen = openProp !== void 0 ? openProp : internalOpen;
38
+ const setOpen = React.useCallback(
39
+ (next) => {
40
+ if (openProp === void 0) setInternalOpen(next);
41
+ onOpenChange?.(next);
42
+ },
43
+ [openProp, onOpenChange]
44
+ );
45
+ const [activeIndex, setActiveIndex] = React.useState(-1);
46
+ const filter = filterFn ?? defaultFilter;
47
+ const filteredOptions = React.useMemo(() => {
48
+ if (!searchText) return options;
49
+ return options.filter((o) => filter(o, searchText));
50
+ }, [options, searchText, filter]);
51
+ const showCreate = creatable && searchText.length > 0 && !options.some((o) => (o.label ?? o.value).toLowerCase() === searchText.toLowerCase());
52
+ const visibleOptions = React.useMemo(() => {
53
+ if (!showCreate) return filteredOptions;
54
+ const createOption = {
55
+ value: `${CREATE_PREFIX}${searchText}`,
56
+ label: creatableLabel ? creatableLabel(searchText) : searchText
57
+ };
58
+ return [...filteredOptions, createOption];
59
+ }, [filteredOptions, showCreate, searchText, creatableLabel]);
60
+ const prevOptionsLengthRef = React.useRef(visibleOptions.length);
61
+ const prevSearchTextRef = React.useRef(searchText);
62
+ React.useEffect(() => {
63
+ if (searchText !== prevSearchTextRef.current || visibleOptions.length !== prevOptionsLengthRef.current) {
64
+ setActiveIndex(-1);
65
+ prevSearchTextRef.current = searchText;
66
+ prevOptionsLengthRef.current = visibleOptions.length;
67
+ }
68
+ }, [searchText, visibleOptions.length]);
69
+ React.useEffect(() => {
70
+ if (activeIndex < 0 || !listRef.current) return;
71
+ const el = listRef.current.querySelector(`[data-option-index="${activeIndex}"]`);
72
+ if (typeof el?.scrollIntoView === "function") {
73
+ el.scrollIntoView({ block: "nearest" });
74
+ }
75
+ }, [activeIndex]);
76
+ function clearInputText() {
77
+ if (inputValueProp === void 0) setInternalInputValue("");
78
+ onInputChange?.("");
79
+ }
80
+ function selectSingle(val) {
81
+ if (!isMulti && props.value === void 0) setInternalValue(val);
82
+ if (!isMulti) props.onChange?.(val);
83
+ }
84
+ function toggleMulti(val) {
85
+ const next = selectedMultiValues.includes(val) ? selectedMultiValues.filter((v) => v !== val) : [...selectedMultiValues, val];
86
+ if (isMulti && props.value === void 0) setInternalMultiValue(next);
87
+ if (isMulti) props.onChange?.(next);
88
+ }
89
+ function handleSelect(option) {
90
+ if (option.value.startsWith(CREATE_PREFIX)) {
91
+ const raw = option.value.slice(CREATE_PREFIX.length);
92
+ onCreate?.(raw);
93
+ if (isMulti) {
94
+ toggleMulti(raw);
95
+ } else {
96
+ selectSingle(raw);
97
+ setOpen(false);
98
+ }
99
+ clearInputText();
100
+ return;
101
+ }
102
+ if (isMulti) {
103
+ toggleMulti(option.value);
104
+ clearInputText();
105
+ } else {
106
+ selectSingle(option.value);
107
+ clearInputText();
108
+ setOpen(false);
109
+ }
110
+ }
111
+ function handleInputChange(e) {
112
+ const val = e.target.value;
113
+ if (inputValueProp === void 0) setInternalInputValue(val);
114
+ onInputChange?.(val);
115
+ if (!isOpen) setOpen(true);
116
+ }
117
+ function findNextEnabled(start, direction) {
118
+ let idx = start;
119
+ while (idx >= 0 && idx < visibleOptions.length && visibleOptions[idx]?.disabled) {
120
+ idx += direction;
121
+ }
122
+ return idx >= 0 && idx < visibleOptions.length ? idx : null;
123
+ }
124
+ function handleKeyDown(e) {
125
+ if (disabled) return;
126
+ switch (e.key) {
127
+ case "ArrowDown": {
128
+ e.preventDefault();
129
+ if (!isOpen) {
130
+ setOpen(true);
131
+ return;
132
+ }
133
+ setActiveIndex((prev) => findNextEnabled(prev + 1, 1) ?? prev);
134
+ break;
135
+ }
136
+ case "ArrowUp": {
137
+ e.preventDefault();
138
+ if (!isOpen) {
139
+ setOpen(true);
140
+ return;
141
+ }
142
+ setActiveIndex((prev) => findNextEnabled(prev - 1, -1) ?? prev);
143
+ break;
144
+ }
145
+ case "Enter": {
146
+ if (isOpen && activeIndex >= 0 && activeIndex < visibleOptions.length) {
147
+ e.preventDefault();
148
+ const opt = visibleOptions[activeIndex];
149
+ if (opt && !opt.disabled) handleSelect(opt);
150
+ }
151
+ break;
152
+ }
153
+ case "Escape": {
154
+ e.preventDefault();
155
+ if (isOpen) setOpen(false);
156
+ break;
157
+ }
158
+ case "Backspace": {
159
+ if (isMulti && searchText === "" && selectedMultiValues.length > 0) {
160
+ const lastVal = selectedMultiValues[selectedMultiValues.length - 1];
161
+ if (lastVal !== void 0) toggleMulti(lastVal);
162
+ }
163
+ break;
164
+ }
165
+ case "Home": {
166
+ if (isOpen) {
167
+ e.preventDefault();
168
+ const idx = findNextEnabled(0, 1);
169
+ if (idx !== null) setActiveIndex(idx);
170
+ }
171
+ break;
172
+ }
173
+ case "End": {
174
+ if (isOpen) {
175
+ e.preventDefault();
176
+ const idx = findNextEnabled(visibleOptions.length - 1, -1);
177
+ if (idx !== null) setActiveIndex(idx);
178
+ }
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ function handleClear(e) {
184
+ e.stopPropagation();
185
+ if (isMulti) {
186
+ if (props.value === void 0) setInternalMultiValue([]);
187
+ if (isMulti) props.onChange?.([]);
188
+ } else {
189
+ selectSingle(null);
190
+ }
191
+ clearInputText();
192
+ inputRef.current?.focus();
193
+ }
194
+ function handleBlur(e) {
195
+ const container = e.currentTarget.closest("[data-autocomplete-root]");
196
+ if (container && e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {
197
+ return;
198
+ }
199
+ if (e.relatedTarget instanceof Node && e.relatedTarget.closest?.("[data-radix-popper-content-wrapper]")) {
200
+ return;
201
+ }
202
+ if (isOpen) setOpen(false);
203
+ }
204
+ function handleFocus() {
205
+ if (!disabled && !isOpen) setOpen(true);
206
+ }
207
+ function handleContainerClick() {
208
+ if (!disabled) {
209
+ inputRef.current?.focus();
210
+ if (!isOpen) setOpen(true);
211
+ }
212
+ }
213
+ function handleOpenChange(next) {
214
+ setOpen(next);
215
+ if (!next) {
216
+ clearInputText();
217
+ setActiveIndex(-1);
218
+ }
219
+ }
220
+ const selectedOption = React.useMemo(
221
+ () => options.find((o) => o.value === selectedValue),
222
+ [options, selectedValue]
223
+ );
224
+ const selectedMultiOptions = React.useMemo(
225
+ () => selectedMultiValues.map((v) => options.find((o) => o.value === v)).filter(Boolean),
226
+ [options, selectedMultiValues]
227
+ );
228
+ const hasClearableValue = isMulti ? selectedMultiValues.length > 0 : selectedValue != null;
229
+ const displayInputValue = React.useMemo(() => {
230
+ if (searchText) return searchText;
231
+ if (isMulti || isOpen) return "";
232
+ return selectedOption ? getLabel(selectedOption) : "";
233
+ }, [searchText, isMulti, isOpen, selectedOption]);
234
+ const activeDescendantId = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
235
+ return {
236
+ inputId,
237
+ helperTextId,
238
+ listboxId,
239
+ inputRef,
240
+ listRef,
241
+ isMulti,
242
+ isOpen,
243
+ selectedValue,
244
+ selectedMultiValues,
245
+ selectedMultiOptions,
246
+ searchText,
247
+ visibleOptions,
248
+ activeIndex,
249
+ activeDescendantId,
250
+ hasClearableValue,
251
+ displayInputValue,
252
+ setActiveIndex,
253
+ handleSelect,
254
+ handleInputChange,
255
+ handleKeyDown,
256
+ handleClear,
257
+ handleBlur,
258
+ handleFocus,
259
+ handleContainerClick,
260
+ handleOpenChange,
261
+ toggleMulti
262
+ };
263
+ }
264
+ export {
265
+ useAutocomplete
266
+ };
267
+ //# sourceMappingURL=useAutocomplete.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAutocomplete.mjs","sources":["../../../src/components/Autocomplete/useAutocomplete.ts"],"sourcesContent":["import * as React from \"react\";\nimport type { AutocompleteOption, AutocompleteProps } from \"./Autocomplete\";\nimport { CREATE_PREFIX, defaultFilter, getLabel } from \"./constants\";\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Combobox hook managing interconnected controlled/uncontrolled state\nexport function useAutocomplete(props: AutocompleteProps) {\n const {\n id,\n options,\n disabled = false,\n filterFn,\n creatable = false,\n creatableLabel,\n onCreate,\n inputValue: inputValueProp,\n onInputChange,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n } = props;\n\n const generatedId = React.useId();\n const inputId = id ?? generatedId;\n const helperTextId = `${inputId}-helper`;\n const listboxId = `${inputId}-listbox`;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const listRef = React.useRef<HTMLDivElement>(null);\n\n const isMulti = props.multiple === true;\n\n // --- single-select state ---\n const [internalValue, setInternalValue] = React.useState<string | null>(\n (!isMulti && props.defaultValue) || null,\n );\n const selectedValue: string | null = !isMulti\n ? props.value !== undefined\n ? props.value\n : internalValue\n : null;\n\n // --- multi-select state ---\n const [internalMultiValue, setInternalMultiValue] = React.useState<string[]>(\n isMulti && props.defaultValue ? props.defaultValue : [],\n );\n const selectedMultiValues: string[] = isMulti\n ? props.value !== undefined\n ? props.value\n : internalMultiValue\n : [];\n\n // --- input text ---\n const [internalInputValue, setInternalInputValue] = React.useState(\"\");\n const searchText = inputValueProp !== undefined ? inputValueProp : internalInputValue;\n\n // --- open state ---\n const [internalOpen, setInternalOpen] = React.useState(defaultOpen);\n const isOpen = openProp !== undefined ? openProp : internalOpen;\n\n const setOpen = React.useCallback(\n (next: boolean) => {\n if (openProp === undefined) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [openProp, onOpenChange],\n );\n\n // --- active index ---\n const [activeIndex, setActiveIndex] = React.useState(-1);\n\n // --- filtering ---\n const filter = filterFn ?? defaultFilter;\n\n const filteredOptions = React.useMemo(() => {\n if (!searchText) return options;\n return options.filter((o) => filter(o, searchText));\n }, [options, searchText, filter]);\n\n const showCreate =\n creatable &&\n searchText.length > 0 &&\n !options.some((o) => (o.label ?? o.value).toLowerCase() === searchText.toLowerCase());\n\n const visibleOptions = React.useMemo(() => {\n if (!showCreate) return filteredOptions;\n const createOption: AutocompleteOption = {\n value: `${CREATE_PREFIX}${searchText}`,\n label: creatableLabel ? creatableLabel(searchText) : searchText,\n };\n return [...filteredOptions, createOption];\n }, [filteredOptions, showCreate, searchText, creatableLabel]);\n\n const prevOptionsLengthRef = React.useRef(visibleOptions.length);\n const prevSearchTextRef = React.useRef(searchText);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally reset only when search text or option count actually changes\n React.useEffect(() => {\n if (\n searchText !== prevSearchTextRef.current ||\n visibleOptions.length !== prevOptionsLengthRef.current\n ) {\n setActiveIndex(-1);\n prevSearchTextRef.current = searchText;\n prevOptionsLengthRef.current = visibleOptions.length;\n }\n }, [searchText, visibleOptions.length]);\n\n // --- scroll active option into view ---\n React.useEffect(() => {\n if (activeIndex < 0 || !listRef.current) return;\n const el = listRef.current.querySelector(`[data-option-index=\"${activeIndex}\"]`);\n if (typeof el?.scrollIntoView === \"function\") {\n el.scrollIntoView({ block: \"nearest\" });\n }\n }, [activeIndex]);\n\n // --- helpers ---\n function clearInputText() {\n if (inputValueProp === undefined) setInternalInputValue(\"\");\n onInputChange?.(\"\");\n }\n\n function selectSingle(val: string | null) {\n if (!isMulti && props.value === undefined) setInternalValue(val);\n if (!isMulti) (props as { onChange?: (v: string | null) => void }).onChange?.(val);\n }\n\n function toggleMulti(val: string) {\n const next = selectedMultiValues.includes(val)\n ? selectedMultiValues.filter((v) => v !== val)\n : [...selectedMultiValues, val];\n if (isMulti && props.value === undefined) setInternalMultiValue(next);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.(next);\n }\n\n function handleSelect(option: AutocompleteOption) {\n if (option.value.startsWith(CREATE_PREFIX)) {\n const raw = option.value.slice(CREATE_PREFIX.length);\n onCreate?.(raw);\n if (isMulti) {\n toggleMulti(raw);\n } else {\n selectSingle(raw);\n setOpen(false);\n }\n clearInputText();\n return;\n }\n\n if (isMulti) {\n toggleMulti(option.value);\n clearInputText();\n } else {\n selectSingle(option.value);\n clearInputText();\n setOpen(false);\n }\n }\n\n function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {\n const val = e.target.value;\n if (inputValueProp === undefined) setInternalInputValue(val);\n onInputChange?.(val);\n if (!isOpen) setOpen(true);\n }\n\n function findNextEnabled(start: number, direction: 1 | -1): number | null {\n let idx = start;\n while (idx >= 0 && idx < visibleOptions.length && visibleOptions[idx]?.disabled) {\n idx += direction;\n }\n return idx >= 0 && idx < visibleOptions.length ? idx : null;\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Flat switch is clearer than splitting into separate handler functions\n function handleKeyDown(e: React.KeyboardEvent) {\n if (disabled) return;\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev + 1, 1) ?? prev);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n if (!isOpen) {\n setOpen(true);\n return;\n }\n setActiveIndex((prev) => findNextEnabled(prev - 1, -1) ?? prev);\n break;\n }\n case \"Enter\": {\n if (isOpen && activeIndex >= 0 && activeIndex < visibleOptions.length) {\n e.preventDefault();\n const opt = visibleOptions[activeIndex];\n if (opt && !opt.disabled) handleSelect(opt);\n }\n break;\n }\n case \"Escape\": {\n e.preventDefault();\n if (isOpen) setOpen(false);\n break;\n }\n case \"Backspace\": {\n if (isMulti && searchText === \"\" && selectedMultiValues.length > 0) {\n const lastVal = selectedMultiValues[selectedMultiValues.length - 1];\n if (lastVal !== undefined) toggleMulti(lastVal);\n }\n break;\n }\n case \"Home\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(0, 1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n case \"End\": {\n if (isOpen) {\n e.preventDefault();\n const idx = findNextEnabled(visibleOptions.length - 1, -1);\n if (idx !== null) setActiveIndex(idx);\n }\n break;\n }\n }\n }\n\n function handleClear(e: React.MouseEvent) {\n e.stopPropagation();\n if (isMulti) {\n if (props.value === undefined) setInternalMultiValue([]);\n if (isMulti) (props as { onChange?: (v: string[]) => void }).onChange?.([]);\n } else {\n selectSingle(null);\n }\n clearInputText();\n inputRef.current?.focus();\n }\n\n function handleBlur(e: React.FocusEvent<HTMLInputElement>) {\n const container = e.currentTarget.closest(\"[data-autocomplete-root]\");\n if (container && e.relatedTarget instanceof Node && container.contains(e.relatedTarget)) {\n return;\n }\n if (\n e.relatedTarget instanceof Node &&\n (e.relatedTarget as Element).closest?.(\"[data-radix-popper-content-wrapper]\")\n ) {\n return;\n }\n if (isOpen) setOpen(false);\n }\n\n function handleFocus() {\n if (!disabled && !isOpen) setOpen(true);\n }\n\n function handleContainerClick() {\n if (!disabled) {\n inputRef.current?.focus();\n if (!isOpen) setOpen(true);\n }\n }\n\n function handleOpenChange(next: boolean) {\n setOpen(next);\n if (!next) {\n clearInputText();\n setActiveIndex(-1);\n }\n }\n\n const selectedOption = React.useMemo(\n () => options.find((o) => o.value === selectedValue),\n [options, selectedValue],\n );\n\n const selectedMultiOptions = React.useMemo(\n () =>\n selectedMultiValues\n .map((v) => options.find((o) => o.value === v))\n .filter(Boolean) as AutocompleteOption[],\n [options, selectedMultiValues],\n );\n\n const hasClearableValue = isMulti ? selectedMultiValues.length > 0 : selectedValue != null;\n\n const displayInputValue = React.useMemo(() => {\n if (searchText) return searchText;\n if (isMulti || isOpen) return \"\";\n return selectedOption ? getLabel(selectedOption) : \"\";\n }, [searchText, isMulti, isOpen, selectedOption]);\n\n const activeDescendantId = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined;\n\n return {\n inputId,\n helperTextId,\n listboxId,\n inputRef,\n listRef,\n isMulti,\n isOpen,\n selectedValue,\n selectedMultiValues,\n selectedMultiOptions,\n searchText,\n visibleOptions,\n activeIndex,\n activeDescendantId,\n hasClearableValue,\n displayInputValue,\n setActiveIndex,\n handleSelect,\n handleInputChange,\n handleKeyDown,\n handleClear,\n handleBlur,\n handleFocus,\n handleContainerClick,\n handleOpenChange,\n toggleMulti,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -9,13 +9,13 @@ const AvatarContext = React.createContext({
9
9
  });
10
10
  const STATUS_POSITIONS = {
11
11
  16: { top: -2, right: -2, indicatorSize: "size-2", borderSize: "border" },
12
- 24: { top: 0, right: 0, indicatorSize: "size-2.5", borderSize: "border" },
13
- 32: { top: 0, right: 0, indicatorSize: "size-3", borderSize: "border" },
14
- 40: { top: 0, right: 0, indicatorSize: "size-3.5", borderSize: "border" },
15
- 48: { top: 0, right: 0, indicatorSize: "size-4", borderSize: "border" },
16
- 64: { top: 2, right: 0, indicatorSize: "size-4.5", borderSize: "border" },
17
- 88: { top: 6, right: 4, indicatorSize: "size-5", borderSize: "border-2" },
18
- 148: { top: 14, right: 14, indicatorSize: "size-6", borderSize: "border-2" }
12
+ 24: { top: 0, right: 0, indicatorSize: "size-2", borderSize: "border" },
13
+ 32: { top: 0, right: 0, indicatorSize: "size-2", borderSize: "border" },
14
+ 40: { top: 2, right: 2, indicatorSize: "size-2", borderSize: "border" },
15
+ 48: { top: 5, right: 2, indicatorSize: "size-2", borderSize: "border" },
16
+ 64: { top: 5, right: 1, indicatorSize: "size-3", borderSize: "border" },
17
+ 88: { top: 8, right: 6, indicatorSize: "size-3", borderSize: "border" },
18
+ 148: { top: 15, right: 15, indicatorSize: "size-3", borderSize: "border" }
19
19
  };
20
20
  const AvatarRoot = React.forwardRef(
21
21
  ({
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.mjs","sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["import * as AvatarPrimitive from \"@radix-ui/react-avatar\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Allowed pixel sizes for the avatar. */\nexport type AvatarSize = 16 | 24 | 32 | 40 | 48 | 64 | 88 | 148;\n\nconst AvatarContext = React.createContext<{ size: AvatarSize; NSFWShow: boolean }>({\n size: 40,\n NSFWShow: false,\n});\n\nconst STATUS_POSITIONS: Record<\n AvatarSize,\n { top: number; right: number; indicatorSize: string; borderSize: string }\n> = {\n 16: { top: -2, right: -2, indicatorSize: \"size-2\", borderSize: \"border\" },\n 24: { top: 0, right: 0, indicatorSize: \"size-2.5\", borderSize: \"border\" },\n 32: { top: 0, right: 0, indicatorSize: \"size-3\", borderSize: \"border\" },\n 40: { top: 0, right: 0, indicatorSize: \"size-3.5\", borderSize: \"border\" },\n 48: { top: 0, right: 0, indicatorSize: \"size-4\", borderSize: \"border\" },\n 64: { top: 2, right: 0, indicatorSize: \"size-4.5\", borderSize: \"border\" },\n 88: { top: 6, right: 4, indicatorSize: \"size-5\", borderSize: \"border-2\" },\n 148: { top: 14, right: 14, indicatorSize: \"size-6\", borderSize: \"border-2\" },\n};\n\n/** Shared avatar styling props. */\ninterface AvatarStyleProps {\n /** Pixel size of the avatar. @default 40 */\n size?: AvatarSize;\n /** Whether to show the online-status indicator dot. @default false */\n onlineIndicator?: boolean;\n /** Whether to show the platinum gradient border ring. @default false */\n platinumShow?: boolean;\n /** Whether to apply the NSFW blur filter over the image. @default false */\n NSFWShow?: boolean;\n}\n\n/** Props for the low-level {@link AvatarRoot} compound component. */\nexport interface AvatarRootProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,\n AvatarStyleProps {}\n\n/**\n * Low-level avatar root for custom compositions. Provides size context to\n * child `AvatarImage` and `AvatarFallback` components.\n *\n * Prefer the higher-level {@link Avatar} component for most use cases.\n */\nconst AvatarRoot = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Root>,\n AvatarRootProps\n>(\n (\n {\n className,\n size = 40,\n onlineIndicator = false,\n platinumShow = false,\n NSFWShow = false,\n children,\n ...props\n },\n ref,\n ) => {\n const statusPosition = STATUS_POSITIONS[size];\n\n return (\n <AvatarContext.Provider value={{ size, NSFWShow }}>\n <div className=\"relative inline-flex\">\n <AvatarPrimitive.Root\n ref={ref}\n data-testid=\"avatar\"\n className={cn(\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-neutral-200\",\n size === 16 && \"size-4 text-2xs\",\n size === 24 && \"size-6 text-xs\",\n size === 32 && \"size-8 text-xs\",\n size === 40 && \"size-10 text-sm\",\n size === 48 && \"size-12 text-base\",\n size === 64 && \"size-16 text-xl\",\n size === 88 && \"size-22 text-2xl\",\n size === 148 && \"size-37 text-4xl\",\n className,\n )}\n {...props}\n >\n {children}\n </AvatarPrimitive.Root>\n {platinumShow && (\n <div\n className=\"pointer-events-none absolute inset-0 rounded-full\"\n style={{\n background: `linear-gradient(143deg, #504F54 0%, #B1B1B1 20.3154%, #13181C 37.3727%, #C6C6C8 58.8154%, #FFFFFF 69.3154%, #0C0F14 81.3154%, #696A6E 100%)`,\n WebkitMask: \"radial-gradient(circle closest-side, transparent 96%, black 96%)\",\n mask: \"radial-gradient(circle closest-side, transparent 96%, black 96%)\",\n }}\n aria-hidden=\"true\"\n />\n )}\n {onlineIndicator && (\n <span\n className={cn(\n \"absolute rounded-full border-surface-container bg-brand-accent-default\",\n statusPosition.borderSize,\n statusPosition.indicatorSize,\n )}\n style={{\n top: `${statusPosition.top}px`,\n right: `${statusPosition.right}px`,\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n </AvatarContext.Provider>\n );\n },\n);\n\nAvatarRoot.displayName = \"AvatarRoot\";\n\n/** Props for the {@link AvatarImage} compound component. */\nexport interface AvatarImageProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> {}\n\n/** Renders the avatar image. Automatically applies the NSFW blur when enabled on the parent `AvatarRoot`. */\nconst AvatarImage = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Image>,\n AvatarImageProps\n>(({ className, ...props }, ref) => {\n const { NSFWShow } = React.useContext(AvatarContext);\n return (\n <AvatarPrimitive.Image\n ref={ref}\n className={cn(\"size-full object-cover\", NSFWShow && \"blur-md\", className)}\n {...props}\n />\n );\n});\n\nAvatarImage.displayName = \"AvatarImage\";\n\n/** Props for the {@link AvatarFallback} compound component. */\nexport interface AvatarFallbackProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> {}\n\n/** Renders fallback content (e.g. initials or an icon) when the avatar image has not loaded. */\nconst AvatarFallback = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Fallback>,\n AvatarFallbackProps\n>(({ className, children, ...props }, ref) => (\n <AvatarPrimitive.Fallback\n ref={ref}\n className={cn(\n \"flex size-full items-center justify-center font-semibold text-foreground-default uppercase leading-none\",\n className,\n )}\n delayMs={0}\n {...props}\n >\n {children}\n </AvatarPrimitive.Fallback>\n));\n\nAvatarFallback.displayName = \"AvatarFallback\";\n\nexport interface AvatarProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,\n AvatarStyleProps {\n /** URL of the avatar image. */\n src?: string;\n /** Alt text for the avatar image. @default \"Avatar\" */\n alt?: string;\n /** Fallback content rendered when the image has not loaded (e.g. initials or an icon). */\n fallback?: React.ReactNode;\n}\n\n/**\n * Displays a user avatar with optional online indicator, platinum border, and\n * NSFW blur. Pass `src` and `fallback` for the simple API, or compose your own\n * layout with `AvatarRoot`, `AvatarImage`, and `AvatarFallback` as children.\n *\n * @example\n * ```tsx\n * <Avatar src=\"/photo.jpg\" alt=\"Jane Doe\" fallback=\"JD\" size={48} />\n * ```\n */\nexport const Avatar = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Root>,\n AvatarProps\n>(\n (\n {\n className,\n size = 40,\n src,\n alt,\n fallback,\n onlineIndicator = false,\n platinumShow = false,\n NSFWShow = false,\n children,\n ...props\n },\n ref,\n ) => {\n const rootProps = {\n ref,\n size,\n onlineIndicator,\n platinumShow,\n NSFWShow,\n className,\n ...props,\n };\n\n if (children) {\n return <AvatarRoot {...rootProps}>{children}</AvatarRoot>;\n }\n\n return (\n <AvatarRoot {...rootProps}>\n {src && <AvatarImage src={src} alt={alt ?? \"Avatar\"} />}\n <AvatarFallback>{fallback}</AvatarFallback>\n </AvatarRoot>\n );\n },\n);\n\nAvatar.displayName = \"Avatar\";\n\nexport { AvatarRoot, AvatarImage, AvatarFallback };\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Avatar.mjs","sources":["../../../src/components/Avatar/Avatar.tsx"],"sourcesContent":["import * as AvatarPrimitive from \"@radix-ui/react-avatar\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Allowed pixel sizes for the avatar. */\nexport type AvatarSize = 16 | 24 | 32 | 40 | 48 | 64 | 88 | 148;\n\nconst AvatarContext = React.createContext<{ size: AvatarSize; NSFWShow: boolean }>({\n size: 40,\n NSFWShow: false,\n});\n\nconst STATUS_POSITIONS: Record<\n AvatarSize,\n { top: number; right: number; indicatorSize: string; borderSize: string }\n> = {\n 16: { top: -2, right: -2, indicatorSize: \"size-2\", borderSize: \"border\" },\n 24: { top: 0, right: 0, indicatorSize: \"size-2\", borderSize: \"border\" },\n 32: { top: 0, right: 0, indicatorSize: \"size-2\", borderSize: \"border\" },\n 40: { top: 2, right: 2, indicatorSize: \"size-2\", borderSize: \"border\" },\n 48: { top: 5, right: 2, indicatorSize: \"size-2\", borderSize: \"border\" },\n 64: { top: 5, right: 1, indicatorSize: \"size-3\", borderSize: \"border\" },\n 88: { top: 8, right: 6, indicatorSize: \"size-3\", borderSize: \"border\" },\n 148: { top: 15, right: 15, indicatorSize: \"size-3\", borderSize: \"border\" },\n};\n\n/** Shared avatar styling props. */\ninterface AvatarStyleProps {\n /** Pixel size of the avatar. @default 40 */\n size?: AvatarSize;\n /** Whether to show the online-status indicator dot. @default false */\n onlineIndicator?: boolean;\n /** Whether to show the platinum gradient border ring. @default false */\n platinumShow?: boolean;\n /** Whether to apply the NSFW blur filter over the image. @default false */\n NSFWShow?: boolean;\n}\n\n/** Props for the low-level {@link AvatarRoot} compound component. */\nexport interface AvatarRootProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,\n AvatarStyleProps {}\n\n/**\n * Low-level avatar root for custom compositions. Provides size context to\n * child `AvatarImage` and `AvatarFallback` components.\n *\n * Prefer the higher-level {@link Avatar} component for most use cases.\n */\nconst AvatarRoot = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Root>,\n AvatarRootProps\n>(\n (\n {\n className,\n size = 40,\n onlineIndicator = false,\n platinumShow = false,\n NSFWShow = false,\n children,\n ...props\n },\n ref,\n ) => {\n const statusPosition = STATUS_POSITIONS[size];\n\n return (\n <AvatarContext.Provider value={{ size, NSFWShow }}>\n <div className=\"relative inline-flex\">\n <AvatarPrimitive.Root\n ref={ref}\n data-testid=\"avatar\"\n className={cn(\n \"relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-neutral-200\",\n size === 16 && \"size-4 text-2xs\",\n size === 24 && \"size-6 text-xs\",\n size === 32 && \"size-8 text-xs\",\n size === 40 && \"size-10 text-sm\",\n size === 48 && \"size-12 text-base\",\n size === 64 && \"size-16 text-xl\",\n size === 88 && \"size-22 text-2xl\",\n size === 148 && \"size-37 text-4xl\",\n className,\n )}\n {...props}\n >\n {children}\n </AvatarPrimitive.Root>\n {platinumShow && (\n <div\n className=\"pointer-events-none absolute inset-0 rounded-full\"\n style={{\n background: `linear-gradient(143deg, #504F54 0%, #B1B1B1 20.3154%, #13181C 37.3727%, #C6C6C8 58.8154%, #FFFFFF 69.3154%, #0C0F14 81.3154%, #696A6E 100%)`,\n WebkitMask: \"radial-gradient(circle closest-side, transparent 96%, black 96%)\",\n mask: \"radial-gradient(circle closest-side, transparent 96%, black 96%)\",\n }}\n aria-hidden=\"true\"\n />\n )}\n {onlineIndicator && (\n <span\n className={cn(\n \"absolute rounded-full border-surface-container bg-brand-accent-default\",\n statusPosition.borderSize,\n statusPosition.indicatorSize,\n )}\n style={{\n top: `${statusPosition.top}px`,\n right: `${statusPosition.right}px`,\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n </AvatarContext.Provider>\n );\n },\n);\n\nAvatarRoot.displayName = \"AvatarRoot\";\n\n/** Props for the {@link AvatarImage} compound component. */\nexport interface AvatarImageProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> {}\n\n/** Renders the avatar image. Automatically applies the NSFW blur when enabled on the parent `AvatarRoot`. */\nconst AvatarImage = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Image>,\n AvatarImageProps\n>(({ className, ...props }, ref) => {\n const { NSFWShow } = React.useContext(AvatarContext);\n return (\n <AvatarPrimitive.Image\n ref={ref}\n className={cn(\"size-full object-cover\", NSFWShow && \"blur-md\", className)}\n {...props}\n />\n );\n});\n\nAvatarImage.displayName = \"AvatarImage\";\n\n/** Props for the {@link AvatarFallback} compound component. */\nexport interface AvatarFallbackProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> {}\n\n/** Renders fallback content (e.g. initials or an icon) when the avatar image has not loaded. */\nconst AvatarFallback = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Fallback>,\n AvatarFallbackProps\n>(({ className, children, ...props }, ref) => (\n <AvatarPrimitive.Fallback\n ref={ref}\n className={cn(\n \"flex size-full items-center justify-center font-semibold text-foreground-default uppercase leading-none\",\n className,\n )}\n delayMs={0}\n {...props}\n >\n {children}\n </AvatarPrimitive.Fallback>\n));\n\nAvatarFallback.displayName = \"AvatarFallback\";\n\nexport interface AvatarProps\n extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,\n AvatarStyleProps {\n /** URL of the avatar image. */\n src?: string;\n /** Alt text for the avatar image. @default \"Avatar\" */\n alt?: string;\n /** Fallback content rendered when the image has not loaded (e.g. initials or an icon). */\n fallback?: React.ReactNode;\n}\n\n/**\n * Displays a user avatar with optional online indicator, platinum border, and\n * NSFW blur. Pass `src` and `fallback` for the simple API, or compose your own\n * layout with `AvatarRoot`, `AvatarImage`, and `AvatarFallback` as children.\n *\n * @example\n * ```tsx\n * <Avatar src=\"/photo.jpg\" alt=\"Jane Doe\" fallback=\"JD\" size={48} />\n * ```\n */\nexport const Avatar = React.forwardRef<\n React.ComponentRef<typeof AvatarPrimitive.Root>,\n AvatarProps\n>(\n (\n {\n className,\n size = 40,\n src,\n alt,\n fallback,\n onlineIndicator = false,\n platinumShow = false,\n NSFWShow = false,\n children,\n ...props\n },\n ref,\n ) => {\n const rootProps = {\n ref,\n size,\n onlineIndicator,\n platinumShow,\n NSFWShow,\n className,\n ...props,\n };\n\n if (children) {\n return <AvatarRoot {...rootProps}>{children}</AvatarRoot>;\n }\n\n return (\n <AvatarRoot {...rootProps}>\n {src && <AvatarImage src={src} alt={alt ?? \"Avatar\"} />}\n <AvatarFallback>{fallback}</AvatarFallback>\n </AvatarRoot>\n );\n },\n);\n\nAvatar.displayName = \"Avatar\";\n\nexport { AvatarRoot, AvatarImage, AvatarFallback };\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.mjs";
5
+ const BottomNavigationContext = React.createContext({
6
+ showLabelsOnlyWhenActive: false,
7
+ hideLabels: false
8
+ });
9
+ function useBottomNavigationContext() {
10
+ return React.useContext(BottomNavigationContext);
11
+ }
12
+ const BottomNavigation = React.forwardRef(
13
+ ({
14
+ className,
15
+ children,
16
+ value,
17
+ onValueChange,
18
+ showLabelsOnlyWhenActive = false,
19
+ hideLabels = false,
20
+ hideOnDesktop = false,
21
+ ...props
22
+ }, ref) => {
23
+ const contextValue = React.useMemo(
24
+ () => ({ value, onValueChange, showLabelsOnlyWhenActive, hideLabels }),
25
+ [value, onValueChange, showLabelsOnlyWhenActive, hideLabels]
26
+ );
27
+ return /* @__PURE__ */ jsx(BottomNavigationContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
28
+ "nav",
29
+ {
30
+ ref,
31
+ className: cn(
32
+ "fixed inset-x-0 bottom-0",
33
+ "flex h-[calc(env(safe-area-inset-bottom,0px)+68px)] items-center justify-around",
34
+ "border-neutral-200 border-t bg-surface-page/[.82] backdrop-blur-[16px]",
35
+ "pb-[env(safe-area-inset-bottom,0px)]",
36
+ hideOnDesktop && "md:hidden",
37
+ className
38
+ ),
39
+ style: { zIndex: "var(--fanvue-ui-portal-z-index, 50)", ...props.style },
40
+ ...props,
41
+ children
42
+ }
43
+ ) });
44
+ }
45
+ );
46
+ BottomNavigation.displayName = "BottomNavigation";
47
+ export {
48
+ BottomNavigation,
49
+ useBottomNavigationContext
50
+ };
51
+ //# sourceMappingURL=BottomNavigation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomNavigation.mjs","sources":["../../../src/components/BottomNavigation/BottomNavigation.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nexport interface BottomNavigationProps extends React.HTMLAttributes<HTMLElement> {\n /** The currently selected action value. */\n value?: string;\n /** Called when the selected action changes. */\n onValueChange?: (value: string) => void;\n /** When `true`, labels are only shown on the active action. @default false */\n showLabelsOnlyWhenActive?: boolean;\n /** When `true`, all labels are hidden. @default false */\n hideLabels?: boolean;\n /** When `true`, the navigation bar is hidden on viewports wider than `md` (768 px). @default false */\n hideOnDesktop?: boolean;\n}\n\ninterface BottomNavigationContextValue {\n value?: string;\n onValueChange?: (value: string) => void;\n showLabelsOnlyWhenActive: boolean;\n hideLabels: boolean;\n}\n\nconst BottomNavigationContext = React.createContext<BottomNavigationContextValue>({\n showLabelsOnlyWhenActive: false,\n hideLabels: false,\n});\n\nexport function useBottomNavigationContext(): BottomNavigationContextValue {\n return React.useContext(BottomNavigationContext);\n}\n\nexport const BottomNavigation = React.forwardRef<HTMLElement, BottomNavigationProps>(\n (\n {\n className,\n children,\n value,\n onValueChange,\n showLabelsOnlyWhenActive = false,\n hideLabels = false,\n hideOnDesktop = false,\n ...props\n },\n ref,\n ) => {\n const contextValue = React.useMemo<BottomNavigationContextValue>(\n () => ({ value, onValueChange, showLabelsOnlyWhenActive, hideLabels }),\n [value, onValueChange, showLabelsOnlyWhenActive, hideLabels],\n );\n\n return (\n <BottomNavigationContext.Provider value={contextValue}>\n <nav\n ref={ref}\n className={cn(\n \"fixed inset-x-0 bottom-0\",\n \"flex h-[calc(env(safe-area-inset-bottom,0px)+68px)] items-center justify-around\",\n \"border-neutral-200 border-t bg-surface-page/[.82] backdrop-blur-[16px]\",\n \"pb-[env(safe-area-inset-bottom,0px)]\",\n hideOnDesktop && \"md:hidden\",\n className,\n )}\n style={{ zIndex: \"var(--fanvue-ui-portal-z-index, 50)\", ...props.style }}\n {...props}\n >\n {children}\n </nav>\n </BottomNavigationContext.Provider>\n );\n },\n);\n\nBottomNavigation.displayName = \"BottomNavigation\";\n"],"names":[],"mappings":";;;;AAuBA,MAAM,0BAA0B,MAAM,cAA4C;AAAA,EAChF,0BAA0B;AAAA,EAC1B,YAAY;AACd,CAAC;AAEM,SAAS,6BAA2D;AACzE,SAAO,MAAM,WAAW,uBAAuB;AACjD;AAEO,MAAM,mBAAmB,MAAM;AAAA,EACpC,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,2BAA2B;AAAA,IAC3B,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,eAAe,MAAM;AAAA,MACzB,OAAO,EAAE,OAAO,eAAe,0BAA0B,WAAA;AAAA,MACzD,CAAC,OAAO,eAAe,0BAA0B,UAAU;AAAA,IAAA;AAG7D,WACE,oBAAC,wBAAwB,UAAxB,EAAiC,OAAO,cACvC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,iBAAiB;AAAA,UACjB;AAAA,QAAA;AAAA,QAEF,OAAO,EAAE,QAAQ,uCAAuC,GAAG,MAAM,MAAA;AAAA,QAChE,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA,GAEL;AAAA,EAEJ;AACF;AAEA,iBAAiB,cAAc;"}
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { Slot, Slottable } from "@radix-ui/react-slot";
4
+ import * as React from "react";
5
+ import { cn } from "../../utils/cn.mjs";
6
+ import { useBottomNavigationContext } from "./BottomNavigation.mjs";
7
+ const BottomNavigationAction = React.forwardRef(({ className, value, icon, label, badge, onClick, asChild = false, children, ...props }, ref) => {
8
+ const {
9
+ value: selectedValue,
10
+ onValueChange,
11
+ showLabelsOnlyWhenActive,
12
+ hideLabels
13
+ } = useBottomNavigationContext();
14
+ const isActive = selectedValue === value;
15
+ const showLabel = !hideLabels && (!showLabelsOnlyWhenActive || isActive);
16
+ const handleClick = (e) => {
17
+ onValueChange?.(value);
18
+ onClick?.(e);
19
+ };
20
+ const Comp = asChild ? Slot : "button";
21
+ return /* @__PURE__ */ jsxs(
22
+ Comp,
23
+ {
24
+ ref,
25
+ ...!asChild && { type: "button" },
26
+ "aria-current": isActive ? "page" : void 0,
27
+ "aria-label": !showLabel && label ? label : void 0,
28
+ "data-state": isActive ? "active" : "inactive",
29
+ className: cn(
30
+ "relative flex min-w-0 flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 overflow-hidden px-2 py-2",
31
+ "motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
32
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-page",
33
+ isActive ? "text-brand-accent-default" : "text-foreground-tertiary hover:text-foreground-secondary",
34
+ className
35
+ ),
36
+ onClick: handleClick,
37
+ ...props,
38
+ children: [
39
+ asChild && /* @__PURE__ */ jsx(Slottable, { children }),
40
+ /* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
41
+ /* @__PURE__ */ jsx("span", { className: "flex size-7", "aria-hidden": "true", children: icon }),
42
+ badge && /* @__PURE__ */ jsx("span", { className: "absolute -end-1 -top-0.5", children: badge })
43
+ ] }),
44
+ showLabel && label && /* @__PURE__ */ jsx(
45
+ "span",
46
+ {
47
+ className: cn(
48
+ "typography-semibold-body-xs min-w-0 max-w-full truncate",
49
+ isActive ? "text-brand-accent-default" : "text-foreground-tertiary"
50
+ ),
51
+ children: label
52
+ }
53
+ )
54
+ ]
55
+ }
56
+ );
57
+ });
58
+ BottomNavigationAction.displayName = "BottomNavigationAction";
59
+ export {
60
+ BottomNavigationAction
61
+ };
62
+ //# sourceMappingURL=BottomNavigationAction.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BottomNavigationAction.mjs","sources":["../../../src/components/BottomNavigation/BottomNavigationAction.tsx"],"sourcesContent":["import { Slot, Slottable } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useBottomNavigationContext } from \"./BottomNavigation\";\n\nexport interface BottomNavigationActionProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"value\"> {\n /** Unique value that identifies this action. */\n value: string;\n /** Icon element displayed above the label. */\n icon: React.ReactElement;\n /** Text label displayed below the icon. */\n label?: string;\n /** Optional badge element (e.g. {@link Count}) rendered at the top-end corner of the icon. */\n badge?: React.ReactNode;\n /** Merge props onto a child element instead of rendering a `<button>`. @default false */\n asChild?: boolean;\n}\n\nexport const BottomNavigationAction = React.forwardRef<\n HTMLButtonElement,\n BottomNavigationActionProps\n>(({ className, value, icon, label, badge, onClick, asChild = false, children, ...props }, ref) => {\n const {\n value: selectedValue,\n onValueChange,\n showLabelsOnlyWhenActive,\n hideLabels,\n } = useBottomNavigationContext();\n\n const isActive = selectedValue === value;\n const showLabel = !hideLabels && (!showLabelsOnlyWhenActive || isActive);\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onValueChange?.(value);\n onClick?.(e);\n };\n\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n {...(!asChild && { type: \"button\" as const })}\n aria-current={isActive ? \"page\" : undefined}\n aria-label={!showLabel && label ? label : undefined}\n data-state={isActive ? \"active\" : \"inactive\"}\n className={cn(\n \"relative flex min-w-0 flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 overflow-hidden px-2 py-2\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface-page\",\n isActive\n ? \"text-brand-accent-default\"\n : \"text-foreground-tertiary hover:text-foreground-secondary\",\n className,\n )}\n onClick={handleClick}\n {...props}\n >\n {asChild && <Slottable>{children}</Slottable>}\n <span className=\"relative inline-flex\">\n <span className=\"flex size-7\" aria-hidden=\"true\">\n {icon}\n </span>\n {badge && <span className=\"absolute -end-1 -top-0.5\">{badge}</span>}\n </span>\n {showLabel && label && (\n <span\n className={cn(\n \"typography-semibold-body-xs min-w-0 max-w-full truncate\",\n isActive ? \"text-brand-accent-default\" : \"text-foreground-tertiary\",\n )}\n >\n {label}\n </span>\n )}\n </Comp>\n );\n});\n\nBottomNavigationAction.displayName = \"BottomNavigationAction\";\n"],"names":[],"mappings":";;;;;;AAmBO,MAAM,yBAAyB,MAAM,WAG1C,CAAC,EAAE,WAAW,OAAO,MAAM,OAAO,OAAO,SAAS,UAAU,OAAO,UAAU,GAAG,MAAA,GAAS,QAAQ;AACjG,QAAM;AAAA,IACJ,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE,2BAAA;AAEJ,QAAM,WAAW,kBAAkB;AACnC,QAAM,YAAY,CAAC,eAAe,CAAC,4BAA4B;AAE/D,QAAM,cAAc,CAAC,MAA2C;AAC9D,oBAAgB,KAAK;AACrB,cAAU,CAAC;AAAA,EACb;AAEA,QAAM,OAAO,UAAU,OAAO;AAE9B,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACC,GAAI,CAAC,WAAW,EAAE,MAAM,SAAA;AAAA,MACzB,gBAAc,WAAW,SAAS;AAAA,MAClC,cAAY,CAAC,aAAa,QAAQ,QAAQ;AAAA,MAC1C,cAAY,WAAW,WAAW;AAAA,MAClC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA,WACI,8BACA;AAAA,QACJ;AAAA,MAAA;AAAA,MAEF,SAAS;AAAA,MACR,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,WAAW,oBAAC,aAAW,SAAA,CAAS;AAAA,QACjC,qBAAC,QAAA,EAAK,WAAU,wBACd,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,eAAc,eAAY,QACvC,UAAA,MACH;AAAA,UACC,SAAS,oBAAC,QAAA,EAAK,WAAU,4BAA4B,UAAA,MAAA,CAAM;AAAA,QAAA,GAC9D;AAAA,QACC,aAAa,SACZ;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,WAAW,8BAA8B;AAAA,YAAA;AAAA,YAG1C,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AAED,uBAAuB,cAAc;"}