@suphark/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/components.json +21 -0
- package/package.json +108 -0
- package/scripts/audit-base-ui-props.mjs +179 -0
- package/scripts/audit-tailwind-typography.mjs +100 -0
- package/src/components/design-system/design-system-showcase.tsx +138 -0
- package/src/components/design-system/sections/showcase-data-display.tsx +454 -0
- package/src/components/design-system/sections/showcase-feedback.tsx +273 -0
- package/src/components/design-system/sections/showcase-forms.tsx +373 -0
- package/src/components/design-system/sections/showcase-foundations.tsx +384 -0
- package/src/components/design-system/sections/showcase-guidelines-recipes.tsx +397 -0
- package/src/components/design-system/sections/showcase-overlays-navigation.tsx +479 -0
- package/src/components/design-system/sections/showcase-overview.tsx +368 -0
- package/src/components/design-system/sections/showcase-primitives.tsx +463 -0
- package/src/components/shared/__tests__/multi-select-combobox.test.tsx +180 -0
- package/src/components/shared/__tests__/name-id-fields.test.ts +24 -0
- package/src/components/shared/button/add-new-button.tsx +19 -0
- package/src/components/shared/button/base/action-button.tsx +134 -0
- package/src/components/shared/button/base/link-button.tsx +130 -0
- package/src/components/shared/button/cancel-button.tsx +27 -0
- package/src/components/shared/button/confirm-button.tsx +26 -0
- package/src/components/shared/button/delete-button.tsx +36 -0
- package/src/components/shared/button/floating-save-button.tsx +89 -0
- package/src/components/shared/button/index.ts +13 -0
- package/src/components/shared/button/print-button.tsx +67 -0
- package/src/components/shared/button/refresh-button.tsx +63 -0
- package/src/components/shared/button/save-button.tsx +29 -0
- package/src/components/shared/button/submit-button.tsx +58 -0
- package/src/components/shared/client-redirect.tsx +11 -0
- package/src/components/shared/combobox.tsx +116 -0
- package/src/components/shared/data-list.tsx +65 -0
- package/src/components/shared/date/date-range-picker.tsx +106 -0
- package/src/components/shared/description-list.tsx +27 -0
- package/src/components/shared/empty-row.tsx +13 -0
- package/src/components/shared/empty-state.tsx +31 -0
- package/src/components/shared/env-snippet.tsx +62 -0
- package/src/components/shared/form/__tests__/simple-select.test.tsx +72 -0
- package/src/components/shared/form/form-field-boolean.tsx +93 -0
- package/src/components/shared/form/form-field-color-select.tsx +86 -0
- package/src/components/shared/form/form-field-combobox.tsx +167 -0
- package/src/components/shared/form/form-field-date.tsx +55 -0
- package/src/components/shared/form/form-field-formatted-number.tsx +103 -0
- package/src/components/shared/form/form-field-input.tsx +372 -0
- package/src/components/shared/form/form-field-radio-cards.tsx +127 -0
- package/src/components/shared/form/form-field-radio-group.tsx +56 -0
- package/src/components/shared/form/form-field-select.tsx +312 -0
- package/src/components/shared/form/simple-select.tsx +152 -0
- package/src/components/shared/form-dialog.tsx +109 -0
- package/src/components/shared/form-message.tsx +30 -0
- package/src/components/shared/initials.tsx +33 -0
- package/src/components/shared/layout/error-state.tsx +160 -0
- package/src/components/shared/layout/page-shell.tsx +57 -0
- package/src/components/shared/link/transition-link.tsx +72 -0
- package/src/components/shared/multi-select-combobox.tsx +392 -0
- package/src/components/shared/name-id-fields.tsx +133 -0
- package/src/components/shared/navigation/page-breadcrumb.tsx +100 -0
- package/src/components/shared/notice.tsx +35 -0
- package/src/components/shared/number/formatted-number.tsx +133 -0
- package/src/components/shared/page-header.tsx +25 -0
- package/src/components/shared/page-skeleton.tsx +18 -0
- package/src/components/shared/row-actions.tsx +93 -0
- package/src/components/shared/row-menu.tsx +55 -0
- package/src/components/shared/secret-reveal.tsx +53 -0
- package/src/components/shared/section-card.tsx +44 -0
- package/src/components/shared/stat.tsx +26 -0
- package/src/components/shared/status-badge.tsx +47 -0
- package/src/components/shared/text/primary-secondary-text.tsx +186 -0
- package/src/components/shared/theme/index.ts +4 -0
- package/src/components/shared/theme/theme-menu-items.tsx +43 -0
- package/src/components/shared/theme/theme-provider.tsx +39 -0
- package/src/components/shared/theme/theme-switch.tsx +36 -0
- package/src/components/shared/theme/theme-toggle.tsx +76 -0
- package/src/components/shared/time/client-date-formatter.tsx +188 -0
- package/src/components/shared/ui-text.tsx +47 -0
- package/src/components/ui/__tests__/combobox.test.tsx +40 -0
- package/src/components/ui/__tests__/date-picker.test.tsx +53 -0
- package/src/components/ui/__tests__/slider.test.tsx +18 -0
- package/src/components/ui/__tests__/tabs.test.tsx +33 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert-dialog.tsx +161 -0
- package/src/components/ui/alert.tsx +68 -0
- package/src/components/ui/aspect-ratio.tsx +22 -0
- package/src/components/ui/attachment.tsx +194 -0
- package/src/components/ui/avatar.tsx +92 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +102 -0
- package/src/components/ui/button-group.tsx +77 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/calendar.tsx +184 -0
- package/src/components/ui/card.tsx +87 -0
- package/src/components/ui/carousel.tsx +230 -0
- package/src/components/ui/chart.tsx +338 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +17 -0
- package/src/components/ui/combobox.tsx +272 -0
- package/src/components/ui/command.tsx +180 -0
- package/src/components/ui/context-menu.tsx +244 -0
- package/src/components/ui/date-picker.tsx +308 -0
- package/src/components/ui/dialog.tsx +137 -0
- package/src/components/ui/drawer.tsx +208 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +93 -0
- package/src/components/ui/field.tsx +223 -0
- package/src/components/ui/form.tsx +161 -0
- package/src/components/ui/hover-card.tsx +46 -0
- package/src/components/ui/input-group.tsx +145 -0
- package/src/components/ui/input-otp.tsx +85 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/item.tsx +187 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/menubar.tsx +266 -0
- package/src/components/ui/navigation-menu.tsx +160 -0
- package/src/components/ui/pagination.tsx +117 -0
- package/src/components/ui/popover.tsx +76 -0
- package/src/components/ui/progress.tsx +66 -0
- package/src/components/ui/radio-group.tsx +38 -0
- package/src/components/ui/relative-date.tsx +40 -0
- package/src/components/ui/resizable.tsx +41 -0
- package/src/components/ui/scroll-area.tsx +67 -0
- package/src/components/ui/searchable-select.tsx +124 -0
- package/src/components/ui/select.tsx +189 -0
- package/src/components/ui/separator.tsx +21 -0
- package/src/components/ui/sheet.tsx +124 -0
- package/src/components/ui/sidebar.tsx +691 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +55 -0
- package/src/components/ui/sonner.tsx +45 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/table.tsx +95 -0
- package/src/components/ui/tabs.tsx +79 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/toggle-group.tsx +86 -0
- package/src/components/ui/toggle.tsx +44 -0
- package/src/components/ui/tooltip.tsx +54 -0
- package/src/hooks/use-copy-to-clipboard.ts +27 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/index.ts +156 -0
- package/src/lib/formatters.ts +44 -0
- package/src/lib/permission.ts +75 -0
- package/src/lib/status-colors.ts +52 -0
- package/src/lib/utils.ts +6 -0
- package/src/next.ts +15 -0
- package/src/styles.css +214 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Radio as RadioPrimitive } from "@base-ui/react/radio";
|
|
4
|
+
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
7
|
+
|
|
8
|
+
function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
|
|
9
|
+
return (
|
|
10
|
+
<RadioGroupPrimitive
|
|
11
|
+
data-slot="radio-group"
|
|
12
|
+
className={cn("grid w-full gap-2", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
|
|
19
|
+
return (
|
|
20
|
+
<RadioPrimitive.Root
|
|
21
|
+
data-slot="radio-group-item"
|
|
22
|
+
className={cn(
|
|
23
|
+
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<RadioPrimitive.Indicator
|
|
29
|
+
data-slot="radio-group-indicator"
|
|
30
|
+
className="flex size-4 items-center justify-center"
|
|
31
|
+
>
|
|
32
|
+
<span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
|
|
33
|
+
</RadioPrimitive.Indicator>
|
|
34
|
+
</RadioPrimitive.Root>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { formatRelativeThaiDate, formatThaiFullDate } from "@suphark/ui/lib/formatters";
|
|
4
|
+
import { Calendar } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface RelativeDateProps {
|
|
7
|
+
date: Date | string | number | null | undefined;
|
|
8
|
+
showIcon?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
iconClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function RelativeDate({
|
|
14
|
+
date,
|
|
15
|
+
showIcon = true,
|
|
16
|
+
className = "flex items-center gap-0.5 text-slate-600 font-medium",
|
|
17
|
+
iconClassName = "h-2.5 w-2.5 text-slate-400 shrink-0",
|
|
18
|
+
}: RelativeDateProps) {
|
|
19
|
+
if (!date) {
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
className="flex items-center gap-0.5 font-normal text-slate-400 text-xs italic"
|
|
23
|
+
title="ยังไม่ได้ระบุวันที่อ้างอิงราคา (Price Reference Date)"
|
|
24
|
+
>
|
|
25
|
+
{showIcon && <Calendar className={iconClassName} />}
|
|
26
|
+
ไม่ได้ระบุ
|
|
27
|
+
</span>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const relativeText = formatRelativeThaiDate(date);
|
|
32
|
+
const fullText = formatThaiFullDate(date);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<span className={className} title={`วันที่อ้างอิงราคาจริง: ${fullText}`}>
|
|
36
|
+
{showIcon && <Calendar className={iconClassName} />}
|
|
37
|
+
{relativeText}
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
4
|
+
import * as ResizablePrimitive from "react-resizable-panels";
|
|
5
|
+
|
|
6
|
+
function ResizablePanelGroup({ className, ...props }: ResizablePrimitive.GroupProps) {
|
|
7
|
+
return (
|
|
8
|
+
<ResizablePrimitive.Group
|
|
9
|
+
data-slot="resizable-panel-group"
|
|
10
|
+
className={cn("flex h-full w-full aria-[orientation=vertical]:flex-col", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
|
|
17
|
+
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ResizableHandle({
|
|
21
|
+
withHandle,
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: ResizablePrimitive.SeparatorProps & {
|
|
25
|
+
withHandle?: boolean;
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<ResizablePrimitive.Separator
|
|
29
|
+
data-slot="resizable-handle"
|
|
30
|
+
className={cn(
|
|
31
|
+
"relative flex w-px items-center justify-center bg-border ring-offset-background after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{withHandle && <div className="z-10 flex h-6 w-1 shrink-0 rounded-lg bg-border" />}
|
|
37
|
+
</ResizablePrimitive.Separator>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
6
|
+
|
|
7
|
+
function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<ScrollAreaPrimitive.Root
|
|
10
|
+
data-slot="scroll-area"
|
|
11
|
+
className={cn("relative", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
<ScrollAreaPrimitive.Viewport
|
|
15
|
+
data-slot="scroll-area-viewport"
|
|
16
|
+
className="size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</ScrollAreaPrimitive.Viewport>
|
|
20
|
+
<ScrollBar />
|
|
21
|
+
<ScrollAreaPrimitive.Corner />
|
|
22
|
+
</ScrollAreaPrimitive.Root>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ScrollBar({
|
|
27
|
+
className,
|
|
28
|
+
orientation = "vertical",
|
|
29
|
+
...props
|
|
30
|
+
}: ScrollAreaPrimitive.Scrollbar.Props) {
|
|
31
|
+
return (
|
|
32
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
33
|
+
data-slot="scroll-area-scrollbar"
|
|
34
|
+
data-orientation={orientation}
|
|
35
|
+
orientation={orientation}
|
|
36
|
+
className={cn(
|
|
37
|
+
// Base UI ใส่ทิศทางไว้ที่ `data-orientation` ไม่ใช่ `data-vertical`/`data-horizontal`
|
|
38
|
+
// ของเดิมเขียนตาม Radix ทำให้คลาสความกว้างไม่เคยถูกใช้ แถบเลื่อนจึงกว้างแค่ 2px
|
|
39
|
+
// (เหลือแค่ padding) และหัวจับกว้าง 0 — มองไม่เห็นทั้งแอป
|
|
40
|
+
"group/scrollbar flex touch-none select-none p-px transition-colors",
|
|
41
|
+
"data-[orientation=vertical]:h-full data-[orientation=vertical]:w-2.5",
|
|
42
|
+
"data-[orientation=vertical]:border-l data-[orientation=vertical]:border-l-transparent",
|
|
43
|
+
"data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col",
|
|
44
|
+
"data-[orientation=horizontal]:border-t data-[orientation=horizontal]:border-t-transparent",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
{/*
|
|
50
|
+
Base UI ติด `data-hovering` ให้แถบเลื่อนเมื่อเมาส์อยู่เหนือพื้นที่เลื่อน
|
|
51
|
+
และ `data-scrolling` ระหว่างเลื่อน — ใช้สองตัวนี้เข้มสีหัวจับ
|
|
52
|
+
ปกติจางไว้ไม่ให้รบกวนสายตา แต่พอจะใช้งานจริงต้องเห็นชัดและจับได้ถนัด
|
|
53
|
+
*/}
|
|
54
|
+
<ScrollAreaPrimitive.Thumb
|
|
55
|
+
data-slot="scroll-area-thumb"
|
|
56
|
+
className={cn(
|
|
57
|
+
"relative flex-1 rounded-full bg-border transition-colors",
|
|
58
|
+
"group-data-[hovering]/scrollbar:bg-muted-foreground/60",
|
|
59
|
+
"group-data-[scrolling]/scrollbar:bg-muted-foreground/60",
|
|
60
|
+
"hover:bg-muted-foreground/80 active:bg-muted-foreground",
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { ScrollArea, ScrollBar };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@suphark/ui/components/ui/button";
|
|
4
|
+
import {
|
|
5
|
+
Command,
|
|
6
|
+
CommandEmpty,
|
|
7
|
+
CommandGroup,
|
|
8
|
+
CommandInput,
|
|
9
|
+
CommandItem,
|
|
10
|
+
CommandList,
|
|
11
|
+
} from "@suphark/ui/components/ui/command";
|
|
12
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@suphark/ui/components/ui/popover";
|
|
13
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
14
|
+
import { Check, ChevronsUpDown } from "lucide-react";
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
|
|
17
|
+
export interface SearchableSelectOption {
|
|
18
|
+
value: string;
|
|
19
|
+
label: string;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SearchableSelectProps {
|
|
24
|
+
value: string | null | undefined;
|
|
25
|
+
onChange: (val: string | null) => void;
|
|
26
|
+
options: SearchableSelectOption[];
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
searchPlaceholder?: string;
|
|
29
|
+
emptyText?: string;
|
|
30
|
+
clearLabel?: string;
|
|
31
|
+
allowClear?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
className?: string;
|
|
34
|
+
popoverWidth?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SearchableSelect({
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
options,
|
|
41
|
+
placeholder = "เลือกรายการ...",
|
|
42
|
+
searchPlaceholder = "ค้นหา...",
|
|
43
|
+
emptyText = "ไม่พบข้อมูล",
|
|
44
|
+
clearLabel = "-- ไม่ระบุ (General) --",
|
|
45
|
+
allowClear = true,
|
|
46
|
+
disabled = false,
|
|
47
|
+
className,
|
|
48
|
+
popoverWidth = "w-[240px]",
|
|
49
|
+
}: SearchableSelectProps) {
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
|
|
52
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
56
|
+
<PopoverTrigger
|
|
57
|
+
render={
|
|
58
|
+
<Button
|
|
59
|
+
variant="outline"
|
|
60
|
+
role="combobox"
|
|
61
|
+
aria-expanded={open}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
className={cn(
|
|
64
|
+
"h-8 w-full justify-between bg-background font-normal text-xs",
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<span className="truncate">{selectedOption ? selectedOption.label : placeholder}</span>
|
|
71
|
+
<ChevronsUpDown className="ml-1.5 h-3 w-3 shrink-0 opacity-50" />
|
|
72
|
+
</PopoverTrigger>
|
|
73
|
+
<PopoverContent className={cn("p-0", popoverWidth)} align="start">
|
|
74
|
+
<Command>
|
|
75
|
+
<CommandInput placeholder={searchPlaceholder} className="h-8 text-xs" />
|
|
76
|
+
<CommandList>
|
|
77
|
+
<CommandEmpty className="py-2 text-center text-muted-foreground text-xs">
|
|
78
|
+
{emptyText}
|
|
79
|
+
</CommandEmpty>
|
|
80
|
+
<CommandGroup>
|
|
81
|
+
{allowClear && (
|
|
82
|
+
<CommandItem
|
|
83
|
+
value="none_clear_option"
|
|
84
|
+
onSelect={() => {
|
|
85
|
+
onChange(null);
|
|
86
|
+
setOpen(false);
|
|
87
|
+
}}
|
|
88
|
+
className="font-medium text-muted-foreground text-xs"
|
|
89
|
+
>
|
|
90
|
+
<Check
|
|
91
|
+
className={cn(
|
|
92
|
+
"mr-2 h-3.5 w-3.5",
|
|
93
|
+
!value || value === "null" || value === "none" ? "opacity-100" : "opacity-0",
|
|
94
|
+
)}
|
|
95
|
+
/>
|
|
96
|
+
{clearLabel}
|
|
97
|
+
</CommandItem>
|
|
98
|
+
)}
|
|
99
|
+
{options.map((option) => (
|
|
100
|
+
<CommandItem
|
|
101
|
+
key={option.value}
|
|
102
|
+
value={option.label}
|
|
103
|
+
onSelect={() => {
|
|
104
|
+
onChange(option.value);
|
|
105
|
+
setOpen(false);
|
|
106
|
+
}}
|
|
107
|
+
className="text-xs"
|
|
108
|
+
>
|
|
109
|
+
<Check
|
|
110
|
+
className={cn(
|
|
111
|
+
"mr-2 h-3.5 w-3.5",
|
|
112
|
+
value === option.value ? "opacity-100" : "opacity-0",
|
|
113
|
+
)}
|
|
114
|
+
/>
|
|
115
|
+
<span className="truncate">{option.label}</span>
|
|
116
|
+
</CommandItem>
|
|
117
|
+
))}
|
|
118
|
+
</CommandGroup>
|
|
119
|
+
</CommandList>
|
|
120
|
+
</Command>
|
|
121
|
+
</PopoverContent>
|
|
122
|
+
</Popover>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Select as SelectPrimitive } from "@base-ui/react/select";
|
|
4
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
5
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
6
|
+
import type * as React from "react";
|
|
7
|
+
|
|
8
|
+
const Select = SelectPrimitive.Root;
|
|
9
|
+
|
|
10
|
+
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<SelectPrimitive.Group
|
|
13
|
+
data-slot="select-group"
|
|
14
|
+
className={cn("scroll-my-1 p-1", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
|
|
21
|
+
return (
|
|
22
|
+
<SelectPrimitive.Value
|
|
23
|
+
data-slot="select-value"
|
|
24
|
+
className={cn("flex flex-1 text-left", className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function SelectTrigger({
|
|
31
|
+
className,
|
|
32
|
+
size = "default",
|
|
33
|
+
children,
|
|
34
|
+
...props
|
|
35
|
+
}: SelectPrimitive.Trigger.Props & {
|
|
36
|
+
size?: "sm" | "default";
|
|
37
|
+
}) {
|
|
38
|
+
return (
|
|
39
|
+
<SelectPrimitive.Trigger
|
|
40
|
+
data-slot="select-trigger"
|
|
41
|
+
data-size={size}
|
|
42
|
+
className={cn(
|
|
43
|
+
"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
<SelectPrimitive.Icon
|
|
50
|
+
render={<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />}
|
|
51
|
+
/>
|
|
52
|
+
</SelectPrimitive.Trigger>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SelectContent({
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
side = "bottom",
|
|
60
|
+
sideOffset = 4,
|
|
61
|
+
align = "center",
|
|
62
|
+
alignOffset = 0,
|
|
63
|
+
alignItemWithTrigger = true,
|
|
64
|
+
...props
|
|
65
|
+
}: SelectPrimitive.Popup.Props &
|
|
66
|
+
Pick<
|
|
67
|
+
SelectPrimitive.Positioner.Props,
|
|
68
|
+
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
|
|
69
|
+
>) {
|
|
70
|
+
return (
|
|
71
|
+
<SelectPrimitive.Portal>
|
|
72
|
+
<SelectPrimitive.Positioner
|
|
73
|
+
side={side}
|
|
74
|
+
sideOffset={sideOffset}
|
|
75
|
+
align={align}
|
|
76
|
+
alignOffset={alignOffset}
|
|
77
|
+
alignItemWithTrigger={alignItemWithTrigger}
|
|
78
|
+
className="isolate z-50"
|
|
79
|
+
>
|
|
80
|
+
<SelectPrimitive.Popup
|
|
81
|
+
data-slot="select-content"
|
|
82
|
+
data-align-trigger={alignItemWithTrigger}
|
|
83
|
+
className={cn(
|
|
84
|
+
"data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in",
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
>
|
|
89
|
+
<SelectScrollUpButton />
|
|
90
|
+
<SelectPrimitive.List>{children}</SelectPrimitive.List>
|
|
91
|
+
<SelectScrollDownButton />
|
|
92
|
+
</SelectPrimitive.Popup>
|
|
93
|
+
</SelectPrimitive.Positioner>
|
|
94
|
+
</SelectPrimitive.Portal>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {
|
|
99
|
+
return (
|
|
100
|
+
<SelectPrimitive.GroupLabel
|
|
101
|
+
data-slot="select-label"
|
|
102
|
+
className={cn("px-1.5 py-1 text-muted-foreground text-xs", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {
|
|
109
|
+
return (
|
|
110
|
+
<SelectPrimitive.Item
|
|
111
|
+
data-slot="select-item"
|
|
112
|
+
className={cn(
|
|
113
|
+
"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
{...props}
|
|
117
|
+
>
|
|
118
|
+
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
|
119
|
+
{children}
|
|
120
|
+
</SelectPrimitive.ItemText>
|
|
121
|
+
<SelectPrimitive.ItemIndicator
|
|
122
|
+
render={
|
|
123
|
+
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
<CheckIcon className="pointer-events-none" />
|
|
127
|
+
</SelectPrimitive.ItemIndicator>
|
|
128
|
+
</SelectPrimitive.Item>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {
|
|
133
|
+
return (
|
|
134
|
+
<SelectPrimitive.Separator
|
|
135
|
+
data-slot="select-separator"
|
|
136
|
+
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function SelectScrollUpButton({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
|
|
146
|
+
return (
|
|
147
|
+
<SelectPrimitive.ScrollUpArrow
|
|
148
|
+
data-slot="select-scroll-up-button"
|
|
149
|
+
className={cn(
|
|
150
|
+
"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
151
|
+
className,
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
>
|
|
155
|
+
<ChevronUpIcon />
|
|
156
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function SelectScrollDownButton({
|
|
161
|
+
className,
|
|
162
|
+
...props
|
|
163
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
|
|
164
|
+
return (
|
|
165
|
+
<SelectPrimitive.ScrollDownArrow
|
|
166
|
+
data-slot="select-scroll-down-button"
|
|
167
|
+
className={cn(
|
|
168
|
+
"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
169
|
+
className,
|
|
170
|
+
)}
|
|
171
|
+
{...props}
|
|
172
|
+
>
|
|
173
|
+
<ChevronDownIcon />
|
|
174
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export {
|
|
179
|
+
Select,
|
|
180
|
+
SelectContent,
|
|
181
|
+
SelectGroup,
|
|
182
|
+
SelectItem,
|
|
183
|
+
SelectLabel,
|
|
184
|
+
SelectScrollDownButton,
|
|
185
|
+
SelectScrollUpButton,
|
|
186
|
+
SelectSeparator,
|
|
187
|
+
SelectTrigger,
|
|
188
|
+
SelectValue,
|
|
189
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<SeparatorPrimitive
|
|
10
|
+
data-slot="separator"
|
|
11
|
+
orientation={orientation}
|
|
12
|
+
className={cn(
|
|
13
|
+
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Separator };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
|
|
4
|
+
import { Button } from "@suphark/ui/components/ui/button";
|
|
5
|
+
import { cn } from "@suphark/ui/lib/utils";
|
|
6
|
+
import { XIcon } from "lucide-react";
|
|
7
|
+
import type * as React from "react";
|
|
8
|
+
|
|
9
|
+
function Sheet({ ...props }: SheetPrimitive.Root.Props) {
|
|
10
|
+
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {
|
|
14
|
+
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function SheetClose({ ...props }: SheetPrimitive.Close.Props) {
|
|
18
|
+
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {
|
|
22
|
+
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
|
|
26
|
+
return (
|
|
27
|
+
<SheetPrimitive.Backdrop
|
|
28
|
+
data-slot="sheet-overlay"
|
|
29
|
+
className={cn(
|
|
30
|
+
"fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function SheetContent({
|
|
39
|
+
className,
|
|
40
|
+
children,
|
|
41
|
+
side = "right",
|
|
42
|
+
showCloseButton = true,
|
|
43
|
+
...props
|
|
44
|
+
}: SheetPrimitive.Popup.Props & {
|
|
45
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
46
|
+
showCloseButton?: boolean;
|
|
47
|
+
}) {
|
|
48
|
+
return (
|
|
49
|
+
<SheetPortal>
|
|
50
|
+
<SheetOverlay />
|
|
51
|
+
<SheetPrimitive.Popup
|
|
52
|
+
data-slot="sheet-content"
|
|
53
|
+
data-side={side}
|
|
54
|
+
className={cn(
|
|
55
|
+
"fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
{showCloseButton && (
|
|
62
|
+
<SheetPrimitive.Close
|
|
63
|
+
data-slot="sheet-close"
|
|
64
|
+
render={<Button variant="ghost" className="absolute top-3 right-3" size="icon-sm" />}
|
|
65
|
+
>
|
|
66
|
+
<XIcon />
|
|
67
|
+
<span className="sr-only">Close</span>
|
|
68
|
+
</SheetPrimitive.Close>
|
|
69
|
+
)}
|
|
70
|
+
</SheetPrimitive.Popup>
|
|
71
|
+
</SheetPortal>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
data-slot="sheet-header"
|
|
79
|
+
className={cn("flex flex-col gap-0.5 p-4", className)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
data-slot="sheet-footer"
|
|
89
|
+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
|
|
96
|
+
return (
|
|
97
|
+
<SheetPrimitive.Title
|
|
98
|
+
data-slot="sheet-title"
|
|
99
|
+
className={cn("font-medium text-base text-foreground", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) {
|
|
106
|
+
return (
|
|
107
|
+
<SheetPrimitive.Description
|
|
108
|
+
data-slot="sheet-description"
|
|
109
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
Sheet,
|
|
117
|
+
SheetClose,
|
|
118
|
+
SheetContent,
|
|
119
|
+
SheetDescription,
|
|
120
|
+
SheetFooter,
|
|
121
|
+
SheetHeader,
|
|
122
|
+
SheetTitle,
|
|
123
|
+
SheetTrigger,
|
|
124
|
+
};
|