@neoptocom/neopto-ui 0.4.1 → 0.5.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 (40) hide show
  1. package/CONSUMER_SETUP.md +55 -36
  2. package/README.md +25 -9
  3. package/package.json +6 -1
  4. package/scripts/init.mjs +200 -0
  5. package/src/assets/agent-neopto-dark.svg +9 -0
  6. package/src/assets/agent-neopto.svg +9 -0
  7. package/src/components/Autocomplete.tsx +279 -0
  8. package/src/components/Avatar.tsx +83 -0
  9. package/src/components/AvatarGroup.tsx +53 -0
  10. package/src/components/Button.tsx +51 -0
  11. package/src/components/Chat/AnimatedBgCircle.tsx +51 -0
  12. package/src/components/Chat/AnimatedBgRectangle.tsx +55 -0
  13. package/src/components/Chat/ChatButton.tsx +132 -0
  14. package/src/components/Chat/index.ts +5 -0
  15. package/src/components/Chip.tsx +38 -0
  16. package/src/components/Counter.tsx +69 -0
  17. package/src/components/Icon.tsx +48 -0
  18. package/src/components/IconButton.tsx +89 -0
  19. package/src/components/Input.tsx +29 -0
  20. package/src/components/Modal.tsx +83 -0
  21. package/src/components/Search.tsx +244 -0
  22. package/src/components/Skeleton.tsx +29 -0
  23. package/src/components/Typo.tsx +93 -0
  24. package/src/index.ts +31 -0
  25. package/src/stories/Autocomplete.stories.tsx +41 -0
  26. package/src/stories/Avatar.stories.tsx +38 -0
  27. package/src/stories/AvatarGroup.stories.tsx +46 -0
  28. package/src/stories/Button.stories.tsx +103 -0
  29. package/src/stories/ChatButton.stories.tsx +94 -0
  30. package/src/stories/Chip.stories.tsx +36 -0
  31. package/src/stories/Counter.stories.tsx +35 -0
  32. package/src/stories/Icon.stories.tsx +34 -0
  33. package/src/stories/IconButton.stories.tsx +116 -0
  34. package/src/stories/Input.stories.tsx +38 -0
  35. package/src/stories/Search.stories.tsx +228 -0
  36. package/src/stories/Skeleton.stories.tsx +43 -0
  37. package/src/stories/Typo.stories.tsx +66 -0
  38. package/src/styles/library.css +35 -0
  39. package/src/styles/tailwind.css +36 -0
  40. package/src/styles/tokens.css +72 -0
@@ -0,0 +1,94 @@
1
+ import * as React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { ChatButton } from "../components/Chat";
4
+ import Typo from "../components/Typo";
5
+ import agentLogoDark from "../assets/agent-neopto-dark.svg";
6
+ import agentLogoLight from "../assets/agent-neopto.svg";
7
+
8
+ const meta: Meta<typeof ChatButton> = {
9
+ title: "Components/ChatButton",
10
+ component: ChatButton,
11
+ args: {
12
+ hasNotification: false,
13
+ notificationMessage: "Hello! How can I help you today?",
14
+ logoSrc: agentLogoDark,
15
+ logoAlt: "NeoPTO Agent",
16
+ },
17
+ argTypes: {
18
+ hasNotification: { control: "boolean" },
19
+ notificationMessage: { control: "text" },
20
+ },
21
+ decorators: [
22
+ (Story) => (
23
+ <div className="min-h-screen relative">
24
+ <Story />
25
+ </div>
26
+ ),
27
+ ],
28
+ };
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof ChatButton>;
32
+
33
+ export const Playground: Story = {
34
+ render: (args) => {
35
+ const [chatOpen, setChatOpen] = React.useState(false);
36
+
37
+ return (
38
+ <>
39
+ <ChatButton
40
+ {...args}
41
+ onOpenChat={() => {
42
+ setChatOpen(true);
43
+ console.log("Chat opened!");
44
+ }}
45
+ />
46
+ {chatOpen && (
47
+ <div className="fixed bottom-24 right-8 bg-[var(--surface)] border border-[var(--border)] rounded-lg p-4 shadow-lg z-50">
48
+ <Typo variant="body-md" className="text-[var(--fg)]">Chat opened!</Typo>
49
+ <button
50
+ onClick={() => setChatOpen(false)}
51
+ className="mt-2 underline"
52
+ >
53
+ <Typo variant="label-sm" className="text-[var(--muted-fg)]">Close</Typo>
54
+ </button>
55
+ </div>
56
+ )}
57
+ </>
58
+ );
59
+ },
60
+ };
61
+
62
+ export const WithNotification: Story = {
63
+ args: {
64
+ hasNotification: true,
65
+ notificationMessage: "I have a new update for your project. Would you like to see it?",
66
+ },
67
+ };
68
+
69
+ export const AutoToggleNotification: Story = {
70
+ render: (args) => {
71
+ const [hasNotification, setHasNotification] = React.useState(false);
72
+
73
+ React.useEffect(() => {
74
+ const interval = setInterval(() => {
75
+ setHasNotification((prev) => !prev);
76
+ }, 5000);
77
+
78
+ return () => clearInterval(interval);
79
+ }, []);
80
+
81
+ return (
82
+ <ChatButton
83
+ {...args}
84
+ hasNotification={hasNotification}
85
+ notificationMessage="This notification toggles every 5 seconds!"
86
+ onOpenChat={() => {
87
+ setHasNotification(false);
88
+ console.log("Chat opened!");
89
+ }}
90
+ />
91
+ );
92
+ },
93
+ };
94
+
@@ -0,0 +1,36 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import Chip from "../components/Chip";
3
+
4
+ const meta: Meta<typeof Chip> = {
5
+ title: "Components/Chip",
6
+ component: Chip,
7
+ args: {
8
+ label: "Status",
9
+ variant: "success"
10
+ },
11
+ argTypes: {
12
+ variant: { control: "radio", options: ["success", "warning", "error", "light", "dark"] }
13
+ }
14
+ };
15
+ export default meta;
16
+ type Story = StoryObj<typeof Chip>;
17
+
18
+ export const Playground: Story = {};
19
+
20
+ export const WithIcon: Story = {
21
+ args: { icon: "check", label: "Completed", variant: "success" }
22
+ };
23
+
24
+ export const Variants: Story = {
25
+ render: () => (
26
+ <div className="flex flex-wrap items-center gap-3">
27
+ <Chip variant="success" icon="check" label="Success" />
28
+ <Chip variant="warning" icon="error_outline" label="Warning" />
29
+ <Chip variant="error" icon="error" label="Error" />
30
+ <Chip variant="light" icon="help_outline" label="Info" />
31
+ <Chip variant="dark" icon="schedule" label="Scheduled" />
32
+ </div>
33
+ )
34
+ };
35
+
36
+
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import Counter from "../components/Counter";
3
+
4
+ const meta: Meta<typeof Counter> = {
5
+ title: "Components/Counter",
6
+ component: Counter,
7
+ args: {
8
+ value: 0,
9
+ min: 0,
10
+ max: 99,
11
+ step: 1
12
+ },
13
+ argTypes: {
14
+ value: { control: "number" },
15
+ min: { control: "number" },
16
+ max: { control: "number" },
17
+ step: { control: "number" }
18
+ }
19
+ };
20
+ export default meta;
21
+ type Story = StoryObj<typeof Counter>;
22
+
23
+ export const Playground: Story = {};
24
+ export const Multiple: Story = {
25
+ render: () => (
26
+ <div className="flex flex-wrap items-center gap-3">
27
+ <Counter value={5} />
28
+ <Counter value={10} />
29
+ <Counter value={0} />
30
+ <Counter value={3} />
31
+ <Counter value={7} />
32
+ </div>
33
+ )
34
+ };
35
+
@@ -0,0 +1,34 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import Icon from "../components/Icon";
3
+
4
+ const meta: Meta<typeof Icon> = {
5
+ title: "Components/Icon",
6
+ component: Icon,
7
+ args: { name: "search", size: "md", fill: 0 }
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof Icon>;
11
+
12
+ export const Playground: Story = {};
13
+
14
+ export const Sizes: Story = {
15
+ render: () => (
16
+ <div className="flex items-center gap-4">
17
+ <Icon name="favorite" size="sm" />
18
+ <Icon name="favorite" size="md" />
19
+ <Icon name="favorite" size="lg" />
20
+ </div>
21
+ )
22
+ };
23
+
24
+ export const WithColorUtilities: Story = {
25
+ render: () => (
26
+ <div className="flex items-center gap-4">
27
+ <Icon name="check" className="text-green-600" />
28
+ <Icon name="error" className="text-red-600" />
29
+ <Icon name="info" className="text-sky-600" />
30
+ </div>
31
+ )
32
+ };
33
+
34
+
@@ -0,0 +1,116 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { IconButton } from "../components/IconButton";
3
+
4
+ const meta: Meta<typeof IconButton> = {
5
+ title: "Components/IconButton",
6
+ component: IconButton,
7
+ args: {
8
+ icon: "search",
9
+ variant: "ghost",
10
+ size: "md",
11
+ disabled: false,
12
+ iconFill: 0
13
+ },
14
+ argTypes: {
15
+ variant: {
16
+ control: "radio",
17
+ options: ["ghost", "primary", "secondary"]
18
+ },
19
+ size: {
20
+ control: "radio",
21
+ options: ["sm", "md", "lg"]
22
+ },
23
+ iconFill: {
24
+ control: "radio",
25
+ options: [0, 1],
26
+ description: "0 = outlined, 1 = filled"
27
+ },
28
+ disabled: {
29
+ control: "boolean"
30
+ }
31
+ }
32
+ };
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof IconButton>;
36
+
37
+ export const Playground: Story = {};
38
+
39
+ export const Variants: Story = {
40
+ render: () => (
41
+ <div className="flex flex-wrap items-center gap-4">
42
+ <IconButton icon="search" variant="ghost" />
43
+ <IconButton icon="search" variant="primary" />
44
+ <IconButton icon="search" variant="secondary" />
45
+ </div>
46
+ )
47
+ };
48
+
49
+ export const Sizes: Story = {
50
+ render: () => (
51
+ <div className="flex flex-wrap items-center gap-4">
52
+ <IconButton icon="search" size="sm" />
53
+ <IconButton icon="search" size="md" />
54
+ <IconButton icon="search" size="lg" />
55
+ </div>
56
+ )
57
+ };
58
+
59
+ export const CommonIcons: Story = {
60
+ render: () => (
61
+ <div className="flex flex-wrap items-center gap-4">
62
+ <IconButton icon="search" title="Search" />
63
+ <IconButton icon="filter_list" title="Filter" />
64
+ <IconButton icon="close" title="Close" />
65
+ <IconButton icon="menu" title="Menu" />
66
+ <IconButton icon="more_vert" title="More options" />
67
+ <IconButton icon="settings" title="Settings" />
68
+ <IconButton icon="person" title="Profile" />
69
+ <IconButton icon="notifications" title="Notifications" />
70
+ <IconButton icon="favorite" title="Favorite" />
71
+ <IconButton icon="share" title="Share" />
72
+ </div>
73
+ )
74
+ };
75
+
76
+ export const FilledIcons: Story = {
77
+ render: () => (
78
+ <div className="flex flex-wrap items-center gap-4">
79
+ <IconButton icon="favorite" iconFill={0} title="Outlined" />
80
+ <IconButton icon="favorite" iconFill={1} title="Filled" />
81
+ <IconButton icon="star" iconFill={0} title="Outlined" />
82
+ <IconButton icon="star" iconFill={1} title="Filled" />
83
+ <IconButton icon="bookmark" iconFill={0} title="Outlined" />
84
+ <IconButton icon="bookmark" iconFill={1} title="Filled" />
85
+ </div>
86
+ )
87
+ };
88
+
89
+ export const States: Story = {
90
+ render: () => (
91
+ <div className="flex flex-wrap items-center gap-4">
92
+ <IconButton icon="search" />
93
+ <IconButton icon="search" disabled />
94
+ </div>
95
+ )
96
+ };
97
+
98
+ export const AllVariantsAndSizes: Story = {
99
+ render: () => (
100
+ <div className="flex flex-col gap-6">
101
+ {(["ghost", "primary", "secondary"] as const).map((variant) => (
102
+ <div key={variant} className="flex flex-col gap-3">
103
+ <div className="text-sm font-medium text-[var(--fg)] capitalize">
104
+ {variant}
105
+ </div>
106
+ <div className="flex flex-wrap items-center gap-4">
107
+ <IconButton icon="search" variant={variant} size="sm" />
108
+ <IconButton icon="search" variant={variant} size="md" />
109
+ <IconButton icon="search" variant={variant} size="lg" />
110
+ </div>
111
+ </div>
112
+ ))}
113
+ </div>
114
+ )
115
+ };
116
+
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Input } from "../components/Input";
3
+
4
+ const meta: Meta<typeof Input> = {
5
+ title: "Components/Input",
6
+ component: Input
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Input>;
10
+
11
+ export const Default: Story = {
12
+ render: () => (
13
+ <div className="flex flex-col gap-4 w-96">
14
+ <Input placeholder="Enter your email" type="email" />
15
+ <Input placeholder="Enter your password" type="password" />
16
+ <Input placeholder="Search..." />
17
+ </div>
18
+ )
19
+ };
20
+
21
+ export const Disabled: Story = {
22
+ render: () => (
23
+ <div className="flex flex-col gap-4 w-96">
24
+ <Input placeholder="Disabled input" disabled />
25
+ </div>
26
+ )
27
+ };
28
+
29
+ export const Types: Story = {
30
+ render: () => (
31
+ <div className="flex flex-col gap-4 w-96">
32
+ <Input placeholder="Text input" type="text" />
33
+ <Input placeholder="Email input" type="email" />
34
+ <Input placeholder="Password input" type="password" />
35
+ <Input placeholder="Number input" type="number" />
36
+ </div>
37
+ )
38
+ };
@@ -0,0 +1,228 @@
1
+ import * as React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import Search, { type SearchOption } from "../components/Search";
4
+ import Counter from "../components/Counter";
5
+ import Icon from "../components/Icon";
6
+ import Typo from "../components/Typo";
7
+
8
+ const SEARCH_RESULTS: SearchOption[] = [
9
+ { label: "Ada Lovelace", value: "ada", image: "https://images.unsplash.com/photo-1544005313-94ddf0286df2?q=80&w=128&auto=format&fit=facearea&facepad=2" },
10
+ { label: "Alan Turing", value: "turing" },
11
+ { label: "Grace Hopper", value: "hopper", image: "https://images.unsplash.com/photo-1545184180-25d471fe75d8?q=80&w=128&auto=format&fit=facearea&facepad=2" },
12
+ { label: "Edsger Dijkstra", value: "dijkstra" },
13
+ { label: "Barbara Liskov", value: "liskov" },
14
+ { label: "Donald Knuth", value: "knuth" },
15
+ { label: "Margaret Hamilton", value: "hamilton", image: "https://images.unsplash.com/photo-1494790108755-2616b612b786?q=80&w=128&auto=format&fit=facearea&facepad=2" },
16
+ { label: "John von Neumann", value: "neumann" },
17
+ { label: "Gordon Moore", value: "moore" },
18
+ { label: "Andy Grove", value: "grove" },
19
+ { label: "Steve Jobs", value: "jobs", image: "https://images.unsplash.com/photo-1560250097-0b93528c311a?q=80&w=128&auto=format&fit=facearea&facepad=2" },
20
+ { label: "Steve Wozniak", value: "wozniak" },
21
+ { label: "Bill Gates", value: "gates", image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=128&auto=format&fit=facearea&facepad=2" },
22
+ { label: "Paul Allen", value: "allen" },
23
+ { label: "Larry Page", value: "page" },
24
+ { label: "Sergey Brin", value: "brin" },
25
+ { label: "Jeff Bezos", value: "bezos" },
26
+ { label: "Elon Musk", value: "musk", image: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=128&auto=format&fit=facearea&facepad=2" },
27
+ { label: "Mark Zuckerberg", value: "zuckerberg" },
28
+ { label: "Satya Nadella", value: "nadella" },
29
+ { label: "Sundar Pichai", value: "pichai" },
30
+ { label: "Jensen Huang", value: "huang" },
31
+ { label: "Lisa Su", value: "su" },
32
+ ];
33
+
34
+ const meta: Meta<typeof Search> = {
35
+ title: "Components/Search",
36
+ component: Search,
37
+ args: {
38
+ options: SEARCH_RESULTS,
39
+ selectedOption: null,
40
+ disabled: false,
41
+ maxHeight: 180,
42
+ searchDelay: 300
43
+ }
44
+ };
45
+
46
+ export default meta;
47
+ type Story = StoryObj<typeof Search>;
48
+
49
+ export const Playground: Story = {
50
+ render: (args) => {
51
+ const [value, setValue] = React.useState<SearchOption | string | null>(args.selectedOption);
52
+ const [results, setResults] = React.useState<SearchOption[]>([]);
53
+ const [isSearching, setIsSearching] = React.useState(false);
54
+
55
+ const handleSearch = React.useCallback((query: string) => {
56
+ if (query.trim()) {
57
+ setIsSearching(true);
58
+ // Simulate API call delay
59
+ setTimeout(() => {
60
+ const filtered = SEARCH_RESULTS.filter(item =>
61
+ item.label.toLowerCase().includes(query.toLowerCase())
62
+ );
63
+ setResults(filtered);
64
+ setIsSearching(false);
65
+ }, 500);
66
+ } else {
67
+ setResults([]);
68
+ setIsSearching(false);
69
+ }
70
+ }, []);
71
+
72
+ return (
73
+ <div className="max-w-md">
74
+ <Search
75
+ {...args}
76
+ options={isSearching ? [] : results}
77
+ selectedOption={value}
78
+ onSelect={setValue}
79
+ onSearch={handleSearch}
80
+ />
81
+ <div className="mt-3 text-xs text-[var(--muted-fg)]">
82
+ Selected: {typeof value === "string" ? value : value?.label ?? "none"}
83
+ </div>
84
+ {isSearching && (
85
+ <div className="mt-2 text-xs text-[var(--muted-fg)]">
86
+ 🔍 Searching...
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ }
92
+ };
93
+
94
+ export const WithCustomDelay: Story = {
95
+ args: {
96
+ searchDelay: 1000
97
+ },
98
+ render: (args) => {
99
+ const [value, setValue] = React.useState<SearchOption | string | null>(null);
100
+ const [results, setResults] = React.useState<SearchOption[]>([]);
101
+ const [searchCount, setSearchCount] = React.useState(0);
102
+
103
+ const handleSearch = React.useCallback((query: string) => {
104
+ setSearchCount(prev => prev + 1);
105
+ if (query.trim()) {
106
+ const filtered = SEARCH_RESULTS.filter(item =>
107
+ item.label.toLowerCase().includes(query.toLowerCase())
108
+ );
109
+ setResults(filtered);
110
+ } else {
111
+ setResults([]);
112
+ }
113
+ }, []);
114
+
115
+ return (
116
+ <div className="max-w-md">
117
+ <Search
118
+ {...args}
119
+ options={results}
120
+ selectedOption={value}
121
+ onSelect={setValue}
122
+ onSearch={handleSearch}
123
+ />
124
+ <div className="mt-3 text-xs text-[var(--muted-fg)]">
125
+ Search calls made: {searchCount} (1 second delay)
126
+ </div>
127
+ </div>
128
+ );
129
+ }
130
+ };
131
+
132
+ export const Disabled: Story = {
133
+ args: {
134
+ disabled: true,
135
+ options: SEARCH_RESULTS
136
+ }
137
+ };
138
+
139
+ export const WithFilters: Story = {
140
+ render: (args) => {
141
+ const [value, setValue] = React.useState<SearchOption | string | null>(null);
142
+ const [results, setResults] = React.useState<SearchOption[]>([]);
143
+ const [topK, setTopK] = React.useState<number>(20);
144
+ const [totalResults, setTotalResults] = React.useState<number>(20);
145
+ const [showTooltip1, setShowTooltip1] = React.useState(false);
146
+ const [showTooltip2, setShowTooltip2] = React.useState(false);
147
+
148
+ const handleSearch = React.useCallback((query: string) => {
149
+ if (query.trim()) {
150
+ const filtered = SEARCH_RESULTS.filter(item =>
151
+ item.label.toLowerCase().includes(query.toLowerCase())
152
+ );
153
+ setResults(filtered);
154
+ } else {
155
+ setResults([]);
156
+ }
157
+ }, []);
158
+
159
+ return (
160
+ <div className="max-w-md">
161
+ <Search
162
+ {...args}
163
+ options={results}
164
+ selectedOption={value}
165
+ onSelect={setValue}
166
+ onSearch={handleSearch}
167
+ >
168
+ {/* Filter content */}
169
+ <div className="flex flex-col gap-3">
170
+ {/* Semantic Search - Top K */}
171
+ <div className="flex items-center gap-3">
172
+ <Counter
173
+ value={topK}
174
+ onChange={setTopK}
175
+ min={1}
176
+ max={100}
177
+ />
178
+ <Typo variant="label-lg" className="mr-1">Semantic Search – Top K</Typo>
179
+ <div className="relative">
180
+ <div
181
+ onMouseEnter={() => setShowTooltip1(true)}
182
+ onMouseLeave={() => setShowTooltip1(false)}
183
+ >
184
+ <Icon name="help" size="sm" className="text-[var(--muted-fg)] cursor-help" />
185
+ </div>
186
+ {showTooltip1 && (
187
+ <div className="absolute left-0 top-6 z-50 px-2 py-1 bg-[var(--surface)] border border-[var(--border)] rounded shadow-lg text-xs text-[var(--fg)] whitespace-nowrap">
188
+ lorem ipsum dolor sit amet
189
+ </div>
190
+ )}
191
+ </div>
192
+ </div>
193
+
194
+ {/* Total Results */}
195
+ <div className="flex items-center gap-3">
196
+ <Counter
197
+ value={totalResults}
198
+ onChange={setTotalResults}
199
+ min={1}
200
+ max={100}
201
+ />
202
+ <Typo variant="label-lg" className="mr-1">Total Results</Typo>
203
+ <div className="relative">
204
+ <div
205
+ onMouseEnter={() => setShowTooltip2(true)}
206
+ onMouseLeave={() => setShowTooltip2(false)}
207
+ >
208
+ <Icon name="help" size="sm" className="text-[var(--muted-fg)] cursor-help" />
209
+ </div>
210
+ {showTooltip2 && (
211
+ <div className="absolute left-0 top-6 z-50 px-2 py-1 bg-[var(--surface)] border border-[var(--border)] rounded shadow-lg text-xs text-[var(--fg)] whitespace-nowrap">
212
+ lorem ipsum dolor sit amet
213
+ </div>
214
+ )}
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </Search>
219
+ <div className="mt-3 text-xs text-[var(--muted-fg)]">
220
+ Selected: {typeof value === "string" ? value : value?.label ?? "none"}
221
+ </div>
222
+ <div className="mt-1 text-xs text-[var(--muted-fg)]">
223
+ Top K: {topK}, Total Results: {totalResults}
224
+ </div>
225
+ </div>
226
+ );
227
+ }
228
+ };
@@ -0,0 +1,43 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Skeleton } from "../components/Skeleton";
3
+
4
+ const meta: Meta<typeof Skeleton> = {
5
+ title: "Components/Skeleton",
6
+ component: Skeleton
7
+ };
8
+ export default meta;
9
+ type Story = StoryObj<typeof Skeleton>;
10
+
11
+ export const Blocks: Story = {
12
+ render: () => (
13
+ <div className="space-y-3">
14
+ <Skeleton className="h-4 w-48" />
15
+ <Skeleton className="h-4 w-72" />
16
+ <Skeleton className="h-24 w-full rounded-[--radius-lg]" />
17
+ </div>
18
+ )
19
+ };
20
+
21
+ export const FullyRounded: Story = {
22
+ render: () => (
23
+ <div className="flex items-center gap-4">
24
+ <Skeleton rounded="full" className="h-10 w-10" />
25
+ <Skeleton rounded="full" className="h-12 w-12" />
26
+ <Skeleton rounded="full" className="h-16 w-16" />
27
+ </div>
28
+ )
29
+ };
30
+
31
+ export const AvatarWithText: Story = {
32
+ render: () => (
33
+ <div className="flex items-center gap-3">
34
+ <Skeleton rounded="full" className="h-12 w-12" />
35
+ <div className="flex-1 space-y-2">
36
+ <Skeleton className="h-4 w-32" />
37
+ <Skeleton className="h-3 w-48" />
38
+ </div>
39
+ </div>
40
+ )
41
+ };
42
+
43
+