@neoptocom/neopto-ui 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +35 -8
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +35 -8
- package/package.json +1 -1
- package/src/components/Autocomplete.tsx +39 -14
- package/src/components/Breadcrumb.docs.mdx +1 -0
- package/src/components/Breadcrumb.stories.tsx +1 -0
- package/src/components/Button.docs.mdx +1 -0
- package/src/components/Button.stories.tsx +1 -0
- package/src/components/Card.docs.mdx +1 -0
- package/src/stories/Autocomplete.stories.tsx +21 -6
package/dist/index.cjs
CHANGED
|
@@ -728,20 +728,40 @@ function Autocomplete({
|
|
|
728
728
|
const listRef = React3.useRef(null);
|
|
729
729
|
const normalizedOptions = React3.useMemo(() => {
|
|
730
730
|
if (Array.isArray(options) && typeof options[0] === "string") {
|
|
731
|
-
return options.map((str) => ({
|
|
731
|
+
return options.map((str) => ({
|
|
732
|
+
name: str,
|
|
733
|
+
label: str,
|
|
734
|
+
value: str
|
|
735
|
+
}));
|
|
732
736
|
}
|
|
733
|
-
return options
|
|
737
|
+
return options.map((option) => {
|
|
738
|
+
const fallback = option.name ?? option.label ?? "";
|
|
739
|
+
return {
|
|
740
|
+
...option,
|
|
741
|
+
name: option.name ?? fallback,
|
|
742
|
+
label: option.label ?? fallback
|
|
743
|
+
};
|
|
744
|
+
});
|
|
734
745
|
}, [options]);
|
|
735
746
|
const filtered = React3.useMemo(() => {
|
|
736
747
|
const q = searchQuery.trim().toLowerCase();
|
|
737
748
|
if (!q) return normalizedOptions;
|
|
738
|
-
return normalizedOptions.filter((o) =>
|
|
749
|
+
return normalizedOptions.filter((o) => {
|
|
750
|
+
const name = o.name?.toLowerCase() ?? "";
|
|
751
|
+
const label = o.label?.toLowerCase() ?? "";
|
|
752
|
+
return name.includes(q) || label.includes(q);
|
|
753
|
+
});
|
|
739
754
|
}, [normalizedOptions, searchQuery]);
|
|
740
755
|
const anyOptionHasImage = React3.useMemo(
|
|
741
756
|
() => normalizedOptions.some((o) => !!o.image),
|
|
742
757
|
[normalizedOptions]
|
|
743
758
|
);
|
|
744
|
-
const
|
|
759
|
+
const optionDisplay = (option) => {
|
|
760
|
+
if (!option) return "";
|
|
761
|
+
if (typeof option === "string") return option;
|
|
762
|
+
return option.name ?? option.label ?? "";
|
|
763
|
+
};
|
|
764
|
+
const displayValue = selectedOption != null ? optionDisplay(selectedOption) : searchQuery;
|
|
745
765
|
function openList() {
|
|
746
766
|
if (disabled) return;
|
|
747
767
|
setOpen(true);
|
|
@@ -885,7 +905,8 @@ function Autocomplete({
|
|
|
885
905
|
style: { maxHeight },
|
|
886
906
|
children: filtered.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { id: listboxId, role: "listbox", ref: listRef, children: filtered.map((option, index) => {
|
|
887
907
|
const active = index === activeIndex;
|
|
888
|
-
const
|
|
908
|
+
const optionName = option.name ?? option.label ?? "";
|
|
909
|
+
const selected = selectedOption != null && (typeof selectedOption === "string" ? selectedOption === optionName || selectedOption === (option.label ?? "") : (selectedOption.name ?? selectedOption.label ?? "") === optionName);
|
|
889
910
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
890
911
|
"li",
|
|
891
912
|
{
|
|
@@ -901,13 +922,19 @@ function Autocomplete({
|
|
|
901
922
|
onClick: () => handleSelect(option),
|
|
902
923
|
children: [
|
|
903
924
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
904
|
-
anyOptionHasImage && /* @__PURE__ */ jsxRuntime.jsx(
|
|
905
|
-
|
|
925
|
+
anyOptionHasImage && /* @__PURE__ */ jsxRuntime.jsx(
|
|
926
|
+
Avatar,
|
|
927
|
+
{
|
|
928
|
+
name: optionName,
|
|
929
|
+
src: option.image || void 0
|
|
930
|
+
}
|
|
931
|
+
),
|
|
932
|
+
/* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "label-lg", className: "font-normal text-[var(--fg)]", children: optionName })
|
|
906
933
|
] }),
|
|
907
934
|
Array.isArray(option.group) && option.group.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(AvatarGroup, { children: option.group.map((member, i) => /* @__PURE__ */ jsxRuntime.jsx(Avatar, { name: member.name, src: member.image || void 0 }, i)) })
|
|
908
935
|
]
|
|
909
936
|
},
|
|
910
|
-
`${
|
|
937
|
+
`${optionName || index}-${index}`
|
|
911
938
|
);
|
|
912
939
|
}) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(Typo, { variant: "body-sm", className: "text-[var(--muted-fg)]", children: "No results found" }) })
|
|
913
940
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -161,7 +161,9 @@ type SkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
161
161
|
declare function Skeleton({ className, rounded, ...props }: SkeletonProps): react_jsx_runtime.JSX.Element;
|
|
162
162
|
|
|
163
163
|
type AutocompleteOption = {
|
|
164
|
-
|
|
164
|
+
name?: string;
|
|
165
|
+
/** @deprecated Prefer using `name`. */
|
|
166
|
+
label?: string;
|
|
165
167
|
value: any;
|
|
166
168
|
image?: string;
|
|
167
169
|
group?: Array<{
|
package/dist/index.d.ts
CHANGED
|
@@ -161,7 +161,9 @@ type SkeletonProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
161
161
|
declare function Skeleton({ className, rounded, ...props }: SkeletonProps): react_jsx_runtime.JSX.Element;
|
|
162
162
|
|
|
163
163
|
type AutocompleteOption = {
|
|
164
|
-
|
|
164
|
+
name?: string;
|
|
165
|
+
/** @deprecated Prefer using `name`. */
|
|
166
|
+
label?: string;
|
|
165
167
|
value: any;
|
|
166
168
|
image?: string;
|
|
167
169
|
group?: Array<{
|
package/dist/index.js
CHANGED
|
@@ -707,20 +707,40 @@ function Autocomplete({
|
|
|
707
707
|
const listRef = useRef(null);
|
|
708
708
|
const normalizedOptions = useMemo(() => {
|
|
709
709
|
if (Array.isArray(options) && typeof options[0] === "string") {
|
|
710
|
-
return options.map((str) => ({
|
|
710
|
+
return options.map((str) => ({
|
|
711
|
+
name: str,
|
|
712
|
+
label: str,
|
|
713
|
+
value: str
|
|
714
|
+
}));
|
|
711
715
|
}
|
|
712
|
-
return options
|
|
716
|
+
return options.map((option) => {
|
|
717
|
+
const fallback = option.name ?? option.label ?? "";
|
|
718
|
+
return {
|
|
719
|
+
...option,
|
|
720
|
+
name: option.name ?? fallback,
|
|
721
|
+
label: option.label ?? fallback
|
|
722
|
+
};
|
|
723
|
+
});
|
|
713
724
|
}, [options]);
|
|
714
725
|
const filtered = useMemo(() => {
|
|
715
726
|
const q = searchQuery.trim().toLowerCase();
|
|
716
727
|
if (!q) return normalizedOptions;
|
|
717
|
-
return normalizedOptions.filter((o) =>
|
|
728
|
+
return normalizedOptions.filter((o) => {
|
|
729
|
+
const name = o.name?.toLowerCase() ?? "";
|
|
730
|
+
const label = o.label?.toLowerCase() ?? "";
|
|
731
|
+
return name.includes(q) || label.includes(q);
|
|
732
|
+
});
|
|
718
733
|
}, [normalizedOptions, searchQuery]);
|
|
719
734
|
const anyOptionHasImage = useMemo(
|
|
720
735
|
() => normalizedOptions.some((o) => !!o.image),
|
|
721
736
|
[normalizedOptions]
|
|
722
737
|
);
|
|
723
|
-
const
|
|
738
|
+
const optionDisplay = (option) => {
|
|
739
|
+
if (!option) return "";
|
|
740
|
+
if (typeof option === "string") return option;
|
|
741
|
+
return option.name ?? option.label ?? "";
|
|
742
|
+
};
|
|
743
|
+
const displayValue = selectedOption != null ? optionDisplay(selectedOption) : searchQuery;
|
|
724
744
|
function openList() {
|
|
725
745
|
if (disabled) return;
|
|
726
746
|
setOpen(true);
|
|
@@ -864,7 +884,8 @@ function Autocomplete({
|
|
|
864
884
|
style: { maxHeight },
|
|
865
885
|
children: filtered.length > 0 ? /* @__PURE__ */ jsx("ul", { id: listboxId, role: "listbox", ref: listRef, children: filtered.map((option, index) => {
|
|
866
886
|
const active = index === activeIndex;
|
|
867
|
-
const
|
|
887
|
+
const optionName = option.name ?? option.label ?? "";
|
|
888
|
+
const selected = selectedOption != null && (typeof selectedOption === "string" ? selectedOption === optionName || selectedOption === (option.label ?? "") : (selectedOption.name ?? selectedOption.label ?? "") === optionName);
|
|
868
889
|
return /* @__PURE__ */ jsxs(
|
|
869
890
|
"li",
|
|
870
891
|
{
|
|
@@ -880,13 +901,19 @@ function Autocomplete({
|
|
|
880
901
|
onClick: () => handleSelect(option),
|
|
881
902
|
children: [
|
|
882
903
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
883
|
-
anyOptionHasImage && /* @__PURE__ */ jsx(
|
|
884
|
-
|
|
904
|
+
anyOptionHasImage && /* @__PURE__ */ jsx(
|
|
905
|
+
Avatar,
|
|
906
|
+
{
|
|
907
|
+
name: optionName,
|
|
908
|
+
src: option.image || void 0
|
|
909
|
+
}
|
|
910
|
+
),
|
|
911
|
+
/* @__PURE__ */ jsx(Typo, { variant: "label-lg", className: "font-normal text-[var(--fg)]", children: optionName })
|
|
885
912
|
] }),
|
|
886
913
|
Array.isArray(option.group) && option.group.length > 0 && /* @__PURE__ */ jsx(AvatarGroup, { children: option.group.map((member, i) => /* @__PURE__ */ jsx(Avatar, { name: member.name, src: member.image || void 0 }, i)) })
|
|
887
914
|
]
|
|
888
915
|
},
|
|
889
|
-
`${
|
|
916
|
+
`${optionName || index}-${index}`
|
|
890
917
|
);
|
|
891
918
|
}) }) : /* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Typo, { variant: "body-sm", className: "text-[var(--muted-fg)]", children: "No results found" }) })
|
|
892
919
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neoptocom/neopto-ui",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
|
|
6
6
|
"keywords": [
|
|
@@ -6,7 +6,9 @@ import Avatar from "./Avatar";
|
|
|
6
6
|
import AvatarGroup from "./AvatarGroup";
|
|
7
7
|
|
|
8
8
|
export type AutocompleteOption = {
|
|
9
|
-
|
|
9
|
+
name?: string;
|
|
10
|
+
/** @deprecated Prefer using `name`. */
|
|
11
|
+
label?: string;
|
|
10
12
|
value: any;
|
|
11
13
|
image?: string;
|
|
12
14
|
group?: Array<{ name: string; image?: string }>;
|
|
@@ -55,16 +57,31 @@ export default function Autocomplete({
|
|
|
55
57
|
// Normalize options
|
|
56
58
|
const normalizedOptions: AutocompleteOption[] = useMemo(() => {
|
|
57
59
|
if (Array.isArray(options) && typeof options[0] === "string") {
|
|
58
|
-
return (options as string[]).map((str) => ({
|
|
60
|
+
return (options as string[]).map((str) => ({
|
|
61
|
+
name: str,
|
|
62
|
+
label: str,
|
|
63
|
+
value: str
|
|
64
|
+
}));
|
|
59
65
|
}
|
|
60
|
-
return options as AutocompleteOption[]
|
|
66
|
+
return (options as AutocompleteOption[]).map((option) => {
|
|
67
|
+
const fallback = option.name ?? option.label ?? "";
|
|
68
|
+
return {
|
|
69
|
+
...option,
|
|
70
|
+
name: option.name ?? fallback,
|
|
71
|
+
label: option.label ?? fallback
|
|
72
|
+
};
|
|
73
|
+
});
|
|
61
74
|
}, [options]);
|
|
62
75
|
|
|
63
76
|
// Filter options
|
|
64
77
|
const filtered = useMemo(() => {
|
|
65
78
|
const q = searchQuery.trim().toLowerCase();
|
|
66
79
|
if (!q) return normalizedOptions;
|
|
67
|
-
return normalizedOptions.filter((o) =>
|
|
80
|
+
return normalizedOptions.filter((o) => {
|
|
81
|
+
const name = o.name?.toLowerCase() ?? "";
|
|
82
|
+
const label = o.label?.toLowerCase() ?? "";
|
|
83
|
+
return name.includes(q) || label.includes(q);
|
|
84
|
+
});
|
|
68
85
|
}, [normalizedOptions, searchQuery]);
|
|
69
86
|
|
|
70
87
|
const anyOptionHasImage = useMemo(
|
|
@@ -72,12 +89,14 @@ export default function Autocomplete({
|
|
|
72
89
|
[normalizedOptions]
|
|
73
90
|
);
|
|
74
91
|
|
|
92
|
+
const optionDisplay = (option?: AutocompleteOption | string | null) => {
|
|
93
|
+
if (!option) return "";
|
|
94
|
+
if (typeof option === "string") return option;
|
|
95
|
+
return option.name ?? option.label ?? "";
|
|
96
|
+
};
|
|
97
|
+
|
|
75
98
|
const displayValue =
|
|
76
|
-
selectedOption != null
|
|
77
|
-
? typeof selectedOption === "string"
|
|
78
|
-
? selectedOption
|
|
79
|
-
: selectedOption.label
|
|
80
|
-
: searchQuery;
|
|
99
|
+
selectedOption != null ? optionDisplay(selectedOption) : searchQuery;
|
|
81
100
|
|
|
82
101
|
function openList() {
|
|
83
102
|
if (disabled) return;
|
|
@@ -225,14 +244,17 @@ export default function Autocomplete({
|
|
|
225
244
|
<ul id={listboxId} role="listbox" ref={listRef}>
|
|
226
245
|
{filtered.map((option, index) => {
|
|
227
246
|
const active = index === activeIndex;
|
|
247
|
+
const optionName = option.name ?? option.label ?? "";
|
|
228
248
|
const selected =
|
|
229
249
|
selectedOption != null &&
|
|
230
250
|
(typeof selectedOption === "string"
|
|
231
|
-
? selectedOption ===
|
|
232
|
-
|
|
251
|
+
? selectedOption === optionName ||
|
|
252
|
+
selectedOption === (option.label ?? "")
|
|
253
|
+
: (selectedOption.name ?? selectedOption.label ?? "") ===
|
|
254
|
+
optionName);
|
|
233
255
|
return (
|
|
234
256
|
<li
|
|
235
|
-
key={`${
|
|
257
|
+
key={`${optionName || index}-${index}`}
|
|
236
258
|
role="option"
|
|
237
259
|
aria-selected={selected}
|
|
238
260
|
className={[
|
|
@@ -248,10 +270,13 @@ export default function Autocomplete({
|
|
|
248
270
|
>
|
|
249
271
|
<div className="flex items-center gap-2">
|
|
250
272
|
{anyOptionHasImage && (
|
|
251
|
-
<Avatar
|
|
273
|
+
<Avatar
|
|
274
|
+
name={optionName}
|
|
275
|
+
src={option.image || undefined}
|
|
276
|
+
/>
|
|
252
277
|
)}
|
|
253
278
|
<Typo variant="label-lg" className="font-normal text-[var(--fg)]">
|
|
254
|
-
{
|
|
279
|
+
{optionName}
|
|
255
280
|
</Typo>
|
|
256
281
|
</div>
|
|
257
282
|
{Array.isArray(option.group) && option.group.length > 0 && (
|
|
@@ -3,11 +3,23 @@ import type { Meta, StoryObj } from "@storybook/react";
|
|
|
3
3
|
import Autocomplete, { type AutocompleteOption } from "../components/Autocomplete";
|
|
4
4
|
|
|
5
5
|
const OPTIONS: AutocompleteOption[] = [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
{
|
|
7
|
+
name: "Ada Lovelace",
|
|
8
|
+
label: "Ada Lovelace",
|
|
9
|
+
value: "ada",
|
|
10
|
+
image:
|
|
11
|
+
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=128&auto=format&fit=facearea&facepad=2"
|
|
12
|
+
},
|
|
13
|
+
{ name: "Alan Turing", value: "turing" },
|
|
14
|
+
{
|
|
15
|
+
name: "Grace Hopper",
|
|
16
|
+
label: "Grace Hopper",
|
|
17
|
+
value: "hopper",
|
|
18
|
+
image:
|
|
19
|
+
"https://images.unsplash.com/photo-1545184180-25d471fe75d8?q=80&w=128&auto=format&fit=facearea&facepad=2"
|
|
20
|
+
},
|
|
21
|
+
{ name: "Edsger Dijkstra", value: "dijkstra" },
|
|
22
|
+
{ name: "Barbara Liskov", value: "liskov" }
|
|
11
23
|
];
|
|
12
24
|
|
|
13
25
|
const meta: Meta<typeof Autocomplete> = {
|
|
@@ -33,7 +45,10 @@ export const Playground: Story = {
|
|
|
33
45
|
<div className="max-w-md">
|
|
34
46
|
<Autocomplete {...args} selectedOption={value} onSelect={setValue} />
|
|
35
47
|
<div className="mt-3 text-xs text-[--muted-fg]">
|
|
36
|
-
Selected:
|
|
48
|
+
Selected:{" "}
|
|
49
|
+
{typeof value === "string"
|
|
50
|
+
? value
|
|
51
|
+
: value?.name ?? value?.label ?? "none"}
|
|
37
52
|
</div>
|
|
38
53
|
</div>
|
|
39
54
|
);
|