@bearmenu/ui 0.1.1 → 0.5.1
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/{chunk-2CUFS5LF.js → chunk-2BMJRXQX.js} +1 -1
- package/dist/chunk-HLQ2IRE7.js +279 -0
- package/dist/chunk-IQASCH2V.js +25 -0
- package/dist/{chunk-UXAYXRFN.js → chunk-QJ54Z7TC.js} +15 -1
- package/dist/{chunk-X6GATUI2.js → chunk-UISA4CIG.js} +1 -0
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/alert.d.ts +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/calendar.d.ts +9 -7
- package/dist/components/calendar.js +153 -39
- package/dist/components/card.d.ts +2 -1
- package/dist/components/card.js +1 -1
- package/dist/components/carousel.js +1 -1
- package/dist/components/checkbox.js +3 -25
- package/dist/components/command.d.ts +13 -13
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/empty-state.js +1 -1
- package/dist/components/haptic-button.js +1 -1
- package/dist/components/link-button.js +1 -1
- package/dist/components/multi-select-combobox.js +1 -1
- package/dist/components/multi-select-menu.d.ts +26 -0
- package/dist/components/multi-select-menu.js +71 -0
- package/dist/components/pagination.js +1 -1
- package/dist/components/phone-frame.d.ts +17 -0
- package/dist/components/phone-frame.js +41 -0
- package/dist/components/place-about-tab.d.ts +17 -0
- package/dist/components/place-about-tab.js +6 -0
- package/dist/components/place-card.d.ts +17 -0
- package/dist/components/place-card.js +145 -0
- package/dist/components/place-details-mobile.d.ts +18 -0
- package/dist/components/place-details-mobile.js +187 -0
- package/dist/components/place-types.d.ts +145 -0
- package/dist/components/place-types.js +1 -0
- package/dist/components/search-bar.js +1 -1
- package/dist/components/searchable-select.js +1 -1
- package/dist/components/skeleton-card.js +1 -1
- package/dist/components/skeleton.js +1 -1
- package/dist/components/sliding-panels.js +1 -1
- package/dist/components/sonner.js +11 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/text.d.ts +3 -3
- package/dist/components/toaster.js +1 -1
- package/package.json +28 -9
- package/src/components/accordion.stories.tsx +51 -0
- package/src/components/alert-dialog.stories.tsx +45 -0
- package/src/components/alert.stories.tsx +85 -0
- package/src/components/aspect-ratio.stories.tsx +47 -0
- package/src/components/avatar.stories.tsx +66 -0
- package/src/components/badge.stories.tsx +60 -0
- package/src/components/breadcrumb.stories.tsx +63 -0
- package/src/components/button.stories.tsx +115 -0
- package/src/components/button.tsx +1 -1
- package/src/components/calendar.stories.tsx +47 -0
- package/src/components/calendar.tsx +160 -40
- package/src/components/card.stories.tsx +72 -0
- package/src/components/card.tsx +13 -1
- package/src/components/carousel.stories.tsx +62 -0
- package/src/components/checkbox.stories.tsx +54 -0
- package/src/components/checkbox.tsx +1 -1
- package/src/components/collapsible.stories.tsx +34 -0
- package/src/components/command.stories.tsx +64 -0
- package/src/components/currency-input.stories.tsx +50 -0
- package/src/components/dialog.stories.tsx +75 -0
- package/src/components/drawer.stories.tsx +47 -0
- package/src/components/dropdown-menu.stories.tsx +64 -0
- package/src/components/dropdown-menu.tsx +1 -1
- package/src/components/empty-state.stories.tsx +67 -0
- package/src/components/form.stories.tsx +73 -0
- package/src/components/haptic-button.stories.tsx +41 -0
- package/src/components/input-otp.stories.tsx +42 -0
- package/src/components/input.stories.tsx +41 -0
- package/src/components/label.stories.tsx +35 -0
- package/src/components/markdown-renderer.stories.tsx +45 -0
- package/src/components/multi-select-combobox.stories.tsx +78 -0
- package/src/components/multi-select-menu.tsx +96 -0
- package/src/components/pagination.stories.tsx +48 -0
- package/src/components/phone-frame.stories.tsx +51 -0
- package/src/components/phone-frame.test.tsx +82 -0
- package/src/components/phone-frame.tsx +59 -0
- package/src/components/place-about-tab.stories.tsx +164 -0
- package/src/components/place-about-tab.test.tsx +325 -0
- package/src/components/place-about-tab.tsx +474 -0
- package/src/components/place-card.stories.tsx +120 -0
- package/src/components/place-card.test.tsx +187 -0
- package/src/components/place-card.tsx +187 -0
- package/src/components/place-details-mobile.tsx +251 -0
- package/src/components/place-types.ts +127 -0
- package/src/components/popover.stories.tsx +42 -0
- package/src/components/progress.stories.tsx +35 -0
- package/src/components/radio-group.stories.tsx +46 -0
- package/src/components/rating.stories.tsx +45 -0
- package/src/components/scroll-area.stories.tsx +48 -0
- package/src/components/search-bar.stories.tsx +92 -0
- package/src/components/searchable-select.stories.tsx +55 -0
- package/src/components/select.stories.tsx +92 -0
- package/src/components/separator.stories.tsx +33 -0
- package/src/components/sheet.stories.tsx +95 -0
- package/src/components/skeleton-card.stories.tsx +53 -0
- package/src/components/skeleton.stories.tsx +42 -0
- package/src/components/skeleton.tsx +1 -0
- package/src/components/slider.stories.tsx +27 -0
- package/src/components/sliding-panels.stories.tsx +68 -0
- package/src/components/sonner.tsx +17 -2
- package/src/components/sticky-container.stories.tsx +33 -0
- package/src/components/switch.stories.tsx +35 -0
- package/src/components/table.stories.tsx +60 -0
- package/src/components/tabs.stories.tsx +53 -0
- package/src/components/tabs.tsx +1 -1
- package/src/components/text.stories.tsx +82 -0
- package/src/components/textarea.stories.tsx +35 -0
- package/src/components/toast.stories.tsx +63 -0
- package/src/components/toggle-group.stories.tsx +60 -0
- package/src/components/toggle.stories.tsx +48 -0
- package/src/components/tooltip.stories.tsx +71 -0
- package/src/globals.css +21 -2
- package/src/test/setup.ts +9 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural mirror of the openapi `Place` schema, decoupled from any one app's
|
|
3
|
+
* generated types. Both FE and admin map their domain shapes into this type
|
|
4
|
+
* before passing data to `<PlaceCard>` / `<PlaceAboutTab>`.
|
|
5
|
+
*/
|
|
6
|
+
export type PlacePreview = {
|
|
7
|
+
id: string
|
|
8
|
+
slug: string
|
|
9
|
+
name: string
|
|
10
|
+
description?: string
|
|
11
|
+
generative_summary?: string
|
|
12
|
+
review_summary?: string
|
|
13
|
+
open_now?: boolean
|
|
14
|
+
rating?: { rating: number; count: number }
|
|
15
|
+
review_count?: number
|
|
16
|
+
hours?: PlaceHourBlock[]
|
|
17
|
+
today_schedule?: PlaceHourBlock[]
|
|
18
|
+
location?: {
|
|
19
|
+
address?: string
|
|
20
|
+
geolocation?: { lat: number | string; lon: number | string }
|
|
21
|
+
distance?: { meters?: number }
|
|
22
|
+
}
|
|
23
|
+
price_dishes?: PlacePriceRange
|
|
24
|
+
price_drinks?: PlacePriceRange
|
|
25
|
+
cuisines?: string[]
|
|
26
|
+
amenities?: string[]
|
|
27
|
+
payment_options?: string[]
|
|
28
|
+
accessibility_features?: string[]
|
|
29
|
+
parking_options?: string[]
|
|
30
|
+
landmarks?: Array<{ name: string; distance_meters?: number }>
|
|
31
|
+
areas?: Array<{ name: string }>
|
|
32
|
+
busyness?: Array<{ hour: number; score: number }>
|
|
33
|
+
type?: string
|
|
34
|
+
business_status?: 'operational' | 'closed_temporarily' | 'closed_permanently'
|
|
35
|
+
phone?: string
|
|
36
|
+
website_url?: string
|
|
37
|
+
socials?: { instagram_url?: string; facebook_url?: string }
|
|
38
|
+
/**
|
|
39
|
+
* Pre-resolved thumbnail URL. Callers compute this themselves
|
|
40
|
+
* (FE: `https://cdn.bear.menu/places/${slug}-thumbnail.jpg`,
|
|
41
|
+
* admin: form's primary photo URL or blob URL).
|
|
42
|
+
*/
|
|
43
|
+
thumbnail_url?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type PlaceHourBlock = { opens_at: number; closes_at: number }
|
|
47
|
+
|
|
48
|
+
export type PlacePriceRange = {
|
|
49
|
+
price_range_low?: number
|
|
50
|
+
price_range_high?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Labels passed to `<PlaceCard>`. All translation happens in the caller. */
|
|
54
|
+
export type PlaceCardLabels = {
|
|
55
|
+
open: string
|
|
56
|
+
closed: string
|
|
57
|
+
until: string
|
|
58
|
+
opensAt: string
|
|
59
|
+
currency: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Labels passed to `<PlaceAboutTab>`. All translation happens in the caller.
|
|
64
|
+
*
|
|
65
|
+
* Dynamic suffix maps (`amenityLabels`, etc.) translate API enum values like
|
|
66
|
+
* `wheelchair_accessible_entrance` into human strings. Missing keys fall back
|
|
67
|
+
* to the raw enum value (with underscores → spaces).
|
|
68
|
+
*/
|
|
69
|
+
export type PlaceAboutTabLabels = {
|
|
70
|
+
overview: string
|
|
71
|
+
description: string
|
|
72
|
+
whatPeopleSay: string
|
|
73
|
+
contactAndLocation: string
|
|
74
|
+
address: string
|
|
75
|
+
instagram: string
|
|
76
|
+
facebook: string
|
|
77
|
+
todayHours: string
|
|
78
|
+
statusOpen: string
|
|
79
|
+
statusClosed: string
|
|
80
|
+
typicalBusyness: string
|
|
81
|
+
cuisines: string
|
|
82
|
+
amenities: string
|
|
83
|
+
paymentOptions: string
|
|
84
|
+
accessibility: string
|
|
85
|
+
parking: string
|
|
86
|
+
nearbyLandmarks: string
|
|
87
|
+
neighborhood: string
|
|
88
|
+
businessInformation: string
|
|
89
|
+
type: string
|
|
90
|
+
status: string
|
|
91
|
+
amenityLabels: Record<string, string>
|
|
92
|
+
paymentLabels: Record<string, string>
|
|
93
|
+
accessibilityLabels: Record<string, string>
|
|
94
|
+
parkingLabels: Record<string, string>
|
|
95
|
+
typeLabels: Record<string, string>
|
|
96
|
+
statusLabels: Record<string, string>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Discriminator for `<PlaceAboutTab>`'s `onLinkClick` callback. */
|
|
100
|
+
export type PlaceLinkKind = 'phone' | 'website' | 'instagram' | 'facebook' | 'address'
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Labels passed to `<PlaceDetailsMobile>`. Extends `PlaceAboutTabLabels` with
|
|
104
|
+
* the header/tab-strip strings unique to the mobile place-details surface.
|
|
105
|
+
*/
|
|
106
|
+
export type PlaceDetailsMobileLabels = PlaceAboutTabLabels & {
|
|
107
|
+
/** Tab strip — "About" is highlighted, the rest are visual placeholders. */
|
|
108
|
+
tabs: {
|
|
109
|
+
menu: string
|
|
110
|
+
about: string
|
|
111
|
+
reviews: string
|
|
112
|
+
events: string
|
|
113
|
+
}
|
|
114
|
+
/** Status badge over the hero image and the inline open/closed pill in the info row. */
|
|
115
|
+
statusBadge: {
|
|
116
|
+
openNow: string
|
|
117
|
+
closed: string
|
|
118
|
+
/** Inserted with surrounding " · " separators. e.g. "Closes at" */
|
|
119
|
+
closesAt: string
|
|
120
|
+
opensAt: string
|
|
121
|
+
opensTomorrow: string
|
|
122
|
+
}
|
|
123
|
+
/** Review count formatter. Pass a translator that handles ICU pluralization. */
|
|
124
|
+
reviewCount: (count: number) => string
|
|
125
|
+
/** "No image available" fallback shown when no thumbnail is set. */
|
|
126
|
+
noImageAvailable: string
|
|
127
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
import { Input } from "./input";
|
|
5
|
+
import { Label } from "./label";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/Popover",
|
|
9
|
+
component: Popover,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
} satisfies Meta<typeof Popover>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<Popover>
|
|
19
|
+
<PopoverTrigger asChild>
|
|
20
|
+
<Button variant="outline">Open Popover</Button>
|
|
21
|
+
</PopoverTrigger>
|
|
22
|
+
<PopoverContent>
|
|
23
|
+
<div className="grid gap-4">
|
|
24
|
+
<div className="space-y-2">
|
|
25
|
+
<h4 className="font-medium leading-none">Dimensions</h4>
|
|
26
|
+
<p className="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="grid gap-2">
|
|
29
|
+
<div className="grid grid-cols-3 items-center gap-4">
|
|
30
|
+
<Label htmlFor="width">Width</Label>
|
|
31
|
+
<Input id="width" defaultValue="100%" className="col-span-2 h-8" />
|
|
32
|
+
</div>
|
|
33
|
+
<div className="grid grid-cols-3 items-center gap-4">
|
|
34
|
+
<Label htmlFor="height">Height</Label>
|
|
35
|
+
<Input id="height" defaultValue="25px" className="col-span-2 h-8" />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</PopoverContent>
|
|
40
|
+
</Popover>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Progress } from "./progress";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Progress",
|
|
6
|
+
component: Progress,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof Progress>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => <Progress value={60} className="w-[300px]" />,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Empty: Story = {
|
|
18
|
+
render: () => <Progress value={0} className="w-[300px]" />,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Full: Story = {
|
|
22
|
+
render: () => <Progress value={100} className="w-[300px]" />,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const AllStates: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<div className="flex flex-col gap-4 w-[300px]">
|
|
28
|
+
<Progress value={0} />
|
|
29
|
+
<Progress value={25} />
|
|
30
|
+
<Progress value={50} />
|
|
31
|
+
<Progress value={75} />
|
|
32
|
+
<Progress value={100} />
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { RadioGroup, RadioGroupItem } from "./radio-group";
|
|
3
|
+
import { Label } from "./label";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/RadioGroup",
|
|
7
|
+
component: RadioGroup,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof RadioGroup>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<RadioGroup defaultValue="option-1">
|
|
17
|
+
<div className="flex items-center space-x-2">
|
|
18
|
+
<RadioGroupItem value="option-1" id="r1" />
|
|
19
|
+
<Label htmlFor="r1">Option One</Label>
|
|
20
|
+
</div>
|
|
21
|
+
<div className="flex items-center space-x-2">
|
|
22
|
+
<RadioGroupItem value="option-2" id="r2" />
|
|
23
|
+
<Label htmlFor="r2">Option Two</Label>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="flex items-center space-x-2">
|
|
26
|
+
<RadioGroupItem value="option-3" id="r3" />
|
|
27
|
+
<Label htmlFor="r3">Option Three</Label>
|
|
28
|
+
</div>
|
|
29
|
+
</RadioGroup>
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Disabled: Story = {
|
|
34
|
+
render: () => (
|
|
35
|
+
<RadioGroup defaultValue="option-1" disabled>
|
|
36
|
+
<div className="flex items-center space-x-2">
|
|
37
|
+
<RadioGroupItem value="option-1" id="rd1" />
|
|
38
|
+
<Label htmlFor="rd1">Disabled selected</Label>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="flex items-center space-x-2">
|
|
41
|
+
<RadioGroupItem value="option-2" id="rd2" />
|
|
42
|
+
<Label htmlFor="rd2">Disabled unselected</Label>
|
|
43
|
+
</div>
|
|
44
|
+
</RadioGroup>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Rating } from "./rating";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Rating",
|
|
6
|
+
component: Rating,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof Rating>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => <Rating value={4} reviewCount={128} />,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const HalfStar: Story = {
|
|
18
|
+
render: () => <Rating value={3.5} reviewCount={42} />,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Sizes: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<div className="flex flex-col gap-3">
|
|
24
|
+
<Rating value={4} size="sm" reviewCount={10} />
|
|
25
|
+
<Rating value={4} size="md" reviewCount={10} />
|
|
26
|
+
<Rating value={4} size="lg" reviewCount={10} />
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const NoCount: Story = {
|
|
32
|
+
render: () => <Rating value={5} showCount={false} />,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const AllRatings: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<div className="flex flex-col gap-2">
|
|
38
|
+
<Rating value={1} reviewCount={5} />
|
|
39
|
+
<Rating value={2} reviewCount={12} />
|
|
40
|
+
<Rating value={3} reviewCount={34} />
|
|
41
|
+
<Rating value={4} reviewCount={89} />
|
|
42
|
+
<Rating value={5} reviewCount={201} />
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ScrollArea, ScrollBar } from "./scroll-area";
|
|
3
|
+
import { Separator } from "./separator";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/ScrollArea",
|
|
7
|
+
component: ScrollArea,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof ScrollArea>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
const tags = Array.from({ length: 50 }).map((_, i) => `Item ${i + 1}`);
|
|
15
|
+
|
|
16
|
+
export const Vertical: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<ScrollArea className="h-72 w-48 rounded-md border">
|
|
19
|
+
<div className="p-4">
|
|
20
|
+
<h4 className="mb-4 text-sm font-medium leading-none">Items</h4>
|
|
21
|
+
{tags.map((tag) => (
|
|
22
|
+
<div key={tag}>
|
|
23
|
+
<div className="text-sm">{tag}</div>
|
|
24
|
+
<Separator className="my-2" />
|
|
25
|
+
</div>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
</ScrollArea>
|
|
29
|
+
),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Horizontal: Story = {
|
|
33
|
+
render: () => (
|
|
34
|
+
<ScrollArea className="w-96 whitespace-nowrap rounded-md border">
|
|
35
|
+
<div className="flex w-max space-x-4 p-4">
|
|
36
|
+
{Array.from({ length: 20 }).map((_, i) => (
|
|
37
|
+
<div
|
|
38
|
+
key={i}
|
|
39
|
+
className="w-[150px] shrink-0 rounded-md border p-4 text-center text-sm"
|
|
40
|
+
>
|
|
41
|
+
Card {i + 1}
|
|
42
|
+
</div>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
<ScrollBar orientation="horizontal" />
|
|
46
|
+
</ScrollArea>
|
|
47
|
+
),
|
|
48
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { SearchBar } from "./search-bar";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Components/SearchBar",
|
|
7
|
+
component: SearchBar,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
} satisfies Meta<typeof SearchBar>;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
function SearchBarDemo() {
|
|
15
|
+
const [value, setValue] = useState("");
|
|
16
|
+
return <SearchBar value={value} onChange={setValue} placeholder="Search restaurants..." />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
render: () => <SearchBarDemo />,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Editorial: Story = {
|
|
24
|
+
render: () => {
|
|
25
|
+
const [value, setValue] = useState("");
|
|
26
|
+
return (
|
|
27
|
+
<SearchBar
|
|
28
|
+
value={value}
|
|
29
|
+
onChange={setValue}
|
|
30
|
+
variant="editorial"
|
|
31
|
+
placeholder="Search..."
|
|
32
|
+
className="w-[400px]"
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Primary: Story = {
|
|
39
|
+
render: () => {
|
|
40
|
+
const [value, setValue] = useState("");
|
|
41
|
+
return (
|
|
42
|
+
<SearchBar
|
|
43
|
+
value={value}
|
|
44
|
+
onChange={setValue}
|
|
45
|
+
variant="primary"
|
|
46
|
+
placeholder="Search..."
|
|
47
|
+
className="w-[400px]"
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Secondary: Story = {
|
|
54
|
+
render: () => {
|
|
55
|
+
const [value, setValue] = useState("");
|
|
56
|
+
return (
|
|
57
|
+
<SearchBar
|
|
58
|
+
value={value}
|
|
59
|
+
onChange={setValue}
|
|
60
|
+
variant="secondary"
|
|
61
|
+
placeholder="Search..."
|
|
62
|
+
className="w-[400px]"
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Compact: Story = {
|
|
69
|
+
render: () => {
|
|
70
|
+
const [value, setValue] = useState("");
|
|
71
|
+
return (
|
|
72
|
+
<SearchBar
|
|
73
|
+
value={value}
|
|
74
|
+
onChange={setValue}
|
|
75
|
+
size="compact"
|
|
76
|
+
placeholder="Quick search..."
|
|
77
|
+
className="w-[300px]"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Searching: Story = {
|
|
84
|
+
render: () => (
|
|
85
|
+
<SearchBar
|
|
86
|
+
value="pizza"
|
|
87
|
+
isSearching
|
|
88
|
+
placeholder="Search..."
|
|
89
|
+
className="w-[400px]"
|
|
90
|
+
/>
|
|
91
|
+
),
|
|
92
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { SearchableSelect } from "./searchable-select";
|
|
4
|
+
import { Label } from "./label";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/SearchableSelect",
|
|
8
|
+
component: SearchableSelect,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
} satisfies Meta<typeof SearchableSelect>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof meta>;
|
|
14
|
+
|
|
15
|
+
const frameworks = [
|
|
16
|
+
{ value: "next", label: "Next.js", description: "The React Framework" },
|
|
17
|
+
{ value: "remix", label: "Remix", description: "Full Stack Web Framework" },
|
|
18
|
+
{ value: "astro", label: "Astro", description: "Content-first Framework" },
|
|
19
|
+
{ value: "nuxt", label: "Nuxt", description: "The Intuitive Vue Framework" },
|
|
20
|
+
{ value: "svelte", label: "SvelteKit", description: "Web development, streamlined" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function SearchableSelectDemo() {
|
|
24
|
+
const [value, setValue] = useState<string | undefined>();
|
|
25
|
+
return (
|
|
26
|
+
<div className="w-[300px] space-y-2">
|
|
27
|
+
<Label>Framework</Label>
|
|
28
|
+
<SearchableSelect
|
|
29
|
+
options={frameworks}
|
|
30
|
+
value={value}
|
|
31
|
+
onValueChange={setValue}
|
|
32
|
+
placeholder="Select a framework..."
|
|
33
|
+
searchPlaceholder="Search frameworks..."
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {
|
|
40
|
+
render: () => <SearchableSelectDemo />,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const Disabled: Story = {
|
|
44
|
+
render: () => (
|
|
45
|
+
<div className="w-[300px]">
|
|
46
|
+
<SearchableSelect
|
|
47
|
+
options={frameworks}
|
|
48
|
+
value={undefined}
|
|
49
|
+
onValueChange={() => {}}
|
|
50
|
+
disabled
|
|
51
|
+
placeholder="Disabled..."
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectGroup,
|
|
6
|
+
SelectItem,
|
|
7
|
+
SelectLabel,
|
|
8
|
+
SelectSeparator,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from "./select";
|
|
12
|
+
import { Label } from "./label";
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: "Components/Select",
|
|
16
|
+
tags: ["autodocs"],
|
|
17
|
+
component: Select,
|
|
18
|
+
} satisfies Meta<typeof Select>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<Select>
|
|
26
|
+
<SelectTrigger className="w-[200px]">
|
|
27
|
+
<SelectValue placeholder="Select a fruit" />
|
|
28
|
+
</SelectTrigger>
|
|
29
|
+
<SelectContent>
|
|
30
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
31
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
32
|
+
<SelectItem value="cherry">Cherry</SelectItem>
|
|
33
|
+
<SelectItem value="grape">Grape</SelectItem>
|
|
34
|
+
</SelectContent>
|
|
35
|
+
</Select>
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const WithLabel: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<div className="grid gap-1.5">
|
|
42
|
+
<Label>Country</Label>
|
|
43
|
+
<Select>
|
|
44
|
+
<SelectTrigger className="w-[200px]">
|
|
45
|
+
<SelectValue placeholder="Select country" />
|
|
46
|
+
</SelectTrigger>
|
|
47
|
+
<SelectContent>
|
|
48
|
+
<SelectItem value="us">United States</SelectItem>
|
|
49
|
+
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
50
|
+
<SelectItem value="ro">Romania</SelectItem>
|
|
51
|
+
<SelectItem value="de">Germany</SelectItem>
|
|
52
|
+
</SelectContent>
|
|
53
|
+
</Select>
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const WithGroups: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<Select>
|
|
61
|
+
<SelectTrigger className="w-[200px]">
|
|
62
|
+
<SelectValue placeholder="Select a food" />
|
|
63
|
+
</SelectTrigger>
|
|
64
|
+
<SelectContent>
|
|
65
|
+
<SelectGroup>
|
|
66
|
+
<SelectLabel>Fruits</SelectLabel>
|
|
67
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
68
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
69
|
+
</SelectGroup>
|
|
70
|
+
<SelectSeparator />
|
|
71
|
+
<SelectGroup>
|
|
72
|
+
<SelectLabel>Vegetables</SelectLabel>
|
|
73
|
+
<SelectItem value="carrot">Carrot</SelectItem>
|
|
74
|
+
<SelectItem value="broccoli">Broccoli</SelectItem>
|
|
75
|
+
</SelectGroup>
|
|
76
|
+
</SelectContent>
|
|
77
|
+
</Select>
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Disabled: Story = {
|
|
82
|
+
render: () => (
|
|
83
|
+
<Select disabled>
|
|
84
|
+
<SelectTrigger className="w-[200px]">
|
|
85
|
+
<SelectValue placeholder="Disabled" />
|
|
86
|
+
</SelectTrigger>
|
|
87
|
+
<SelectContent>
|
|
88
|
+
<SelectItem value="a">Option A</SelectItem>
|
|
89
|
+
</SelectContent>
|
|
90
|
+
</Select>
|
|
91
|
+
),
|
|
92
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Separator } from "./separator";
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Separator",
|
|
6
|
+
component: Separator,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
} satisfies Meta<typeof Separator>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj<typeof meta>;
|
|
12
|
+
|
|
13
|
+
export const Horizontal: Story = {
|
|
14
|
+
render: () => (
|
|
15
|
+
<div className="w-[300px]">
|
|
16
|
+
<p className="text-sm text-foreground">Section one</p>
|
|
17
|
+
<Separator className="my-4" />
|
|
18
|
+
<p className="text-sm text-foreground">Section two</p>
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Vertical: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<div className="flex items-center gap-4 h-8">
|
|
26
|
+
<span className="text-sm">Item 1</span>
|
|
27
|
+
<Separator orientation="vertical" />
|
|
28
|
+
<span className="text-sm">Item 2</span>
|
|
29
|
+
<Separator orientation="vertical" />
|
|
30
|
+
<span className="text-sm">Item 3</span>
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
};
|