@suphark/ui 0.25.1 → 0.26.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 +21 -0
- package/package.json +1 -2
- package/src/components/design-system/design-system-showcase.tsx +63 -15
- package/src/components/shared/button/base/action-button.tsx +7 -14
- package/src/components/shared/button/base/link-button.tsx +5 -14
- package/src/components/shared/form/number-input.tsx +9 -2
- package/src/components/shared/form/tags-input.tsx +1 -1
- package/src/components/shared/form/time-picker.tsx +11 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/native-select.tsx +1 -1
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/textarea.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
|
|
4
4
|
|
|
5
|
+
## [0.26.0] — 2026-10-03
|
|
6
|
+
|
|
7
|
+
ปิดงานค้างจากผลรีวิว + prop ที่ DueBell ขอ — เพิ่มอย่างเดียว
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `NumberInput`: `inputRef` (ref ของช่องที่พิมพ์ — focus เองได้) และ `autoFocus` (คำขอจาก DueBell)
|
|
12
|
+
- `TimePicker`: `roundToStep` — ปัดนาทีที่พิมพ์ลงตาม `minuteStep` (เช่น step 60 + พิมพ์ 14:30 → 14:00); ค่าเริ่มต้นเก็บตามที่พิมพ์เหมือนเดิม (คำขอจาก DueBell)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **ช่องกรอกใช้ขนาดตัวอักษรตามบทบาท `text-body`** (16px มือถือ / 15px จอกว้าง) — `Input`, `Textarea`, `SelectTrigger`, `NativeSelect` (+ `NumberInput`, `TagsInput`): เดิม `text-base md:text-sm` = 14px บนจอกว้าง เล็กกว่าข้อความรอบข้าง 1px และ `text-body` ที่แอปใส่ให้ช่องได้ผลแค่มือถือ (pi-done ต้องเติม `md:text-body` เอง 19 จุด — ลบได้แล้ว); `SelectTrigger`/`NativeSelect` เดิม 14px แม้บนมือถือ (iOS ซูมเมื่อโฟกัส) — แก้ไฟล์ shadcn 4 ไฟล์เป็นข้อยกเว้น (เพิ่มในรายการคืนไฟล์หลัง `shadcn add --overwrite` ใน CLAUDE.md); `className` ของแอปยังแทนได้ (`cn` รู้จักคลาสตามบทบาท)
|
|
17
|
+
- ปุ่ม `ActionButton`/`LinkButton` ที่มี tooltip ไม่สร้าง `TooltipProvider` ต่อปุ่มแล้ว (ตาราง 50 แถว = 100+ provider) — ใช้ `delay={0}` ที่ trigger แทน หน้าตา/จังหวะเหมือนเดิม
|
|
18
|
+
- `DesignSystemShowcase`: แท็บอื่นนอกจาก "ภาพรวม" โหลดแบบ lazy เมื่อเปิดแท็บ (เดิม import ทุกแท็บ ~1 MB) — ลิงก์ข้ามแท็บไปหัวข้อรอแท็บโหลดเสร็จก่อนเลื่อน
|
|
19
|
+
- ลบ dependency `cn` ที่ไม่ได้ใช้ (แอปไม่ต้องติดตั้งเพิ่ม)
|
|
20
|
+
- docs: agent-rules — barrel ที่ client import ห้าม re-export schema ของ zod (ค่า) ให้ export แค่ type (DueBell −18% ต่อหน้า); `ChartConfig` ใช้เฉพาะค่าที่แอปกำหนดเอง (ค่าถูกเขียนลง `<style>` ตรง ๆ)
|
|
21
|
+
- CI: `permissions: contents: read` (+ `pull-requests: read` ให้ gitleaks) — GITHUB_TOKEN อ่านได้อย่างเดียว; pin action ด้วย commit SHA (checkout v4.4.0, gitleaks-action v2.3.9, pnpm/action-setup v4.3.0, setup-node v4.4.0)
|
|
22
|
+
- showcase: `/components/[slug]` โหลด demo แบบ `next/dynamic` ต่อ slug (`src/docs/demos.tsx` + `DemoView`) — เดิมทุกหน้าโหลด demo ทั้ง 129 ตัว (~409 kB gzip ต่อหน้า เช่น `/components/badge` พก recharts)
|
|
23
|
+
- showcase: demo `code-block`/`secret-reveal` ใช้ค่า `example_…` แทน `sk_live_…` (ไม่ให้ secret scanner แจ้งเตือนผิด)
|
|
24
|
+
- showcase: `@suphark/auth-client` ^0.1.7 → **^0.6.1** (แก้ open redirect ใน `safeNextPath` ของ handler `/auth/login|callback|logout` — ทดสอบ `/auth/logout?next=/%09/evil.com` แล้วไม่ออกนอกโดเมน); breaking ระหว่างทาง (ลบ Prisma adapter, เปลี่ยนชื่อ `ACCESS_DENIED`) ไม่กระทบ showcase
|
|
25
|
+
|
|
5
26
|
## [0.25.1] — 2026-10-02
|
|
6
27
|
|
|
7
28
|
แก้ตามผลรีวิว codebase (bug / security / performance) — ไม่เปลี่ยน props
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Design system กลางของ Suphark apps: shadcn/ui (base-nova, Base UI, Tailwind v4) + shared components + token/ฟอนต์ Noto Sans Thai Looped — ส่งเป็น source ให้แอป Next transpile เอง",
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
"class-variance-authority": "^0.7.1",
|
|
73
73
|
"clsx": "^2.1.1",
|
|
74
74
|
"cmdk": "^1.1.1",
|
|
75
|
-
"cn": "^0.3.0",
|
|
76
75
|
"date-fns": "^4.4.0",
|
|
77
76
|
"embla-carousel-react": "^8.6.0",
|
|
78
77
|
"input-otp": "^1.5.0",
|
|
@@ -12,14 +12,38 @@ import {
|
|
|
12
12
|
Zap,
|
|
13
13
|
} from "lucide-react";
|
|
14
14
|
import * as React from "react";
|
|
15
|
-
import { ShowcaseDataDisplay } from "./sections/showcase-data-display";
|
|
16
|
-
import { ShowcaseFeedback } from "./sections/showcase-feedback";
|
|
17
|
-
import { ShowcaseForms } from "./sections/showcase-forms";
|
|
18
|
-
import { ShowcaseFoundations } from "./sections/showcase-foundations";
|
|
19
|
-
import { ShowcaseGuidelinesRecipes } from "./sections/showcase-guidelines-recipes";
|
|
20
|
-
import { ShowcaseOverlaysNavigation } from "./sections/showcase-overlays-navigation";
|
|
21
15
|
import { ShowcaseOverview } from "./sections/showcase-overview";
|
|
22
|
-
|
|
16
|
+
|
|
17
|
+
// แท็บอื่นนอกจากภาพรวมโหลดเมื่อเปิดแท็บนั้น — เดิม import ทุกแท็บ (ปฏิทิน, ตาราง, dnd-kit, cmdk …) ~1 MB ทั้งที่เห็นทีละแท็บ
|
|
18
|
+
const ShowcaseDataDisplay = React.lazy(() =>
|
|
19
|
+
import("./sections/showcase-data-display").then((m) => ({ default: m.ShowcaseDataDisplay })),
|
|
20
|
+
);
|
|
21
|
+
const ShowcaseFeedback = React.lazy(() =>
|
|
22
|
+
import("./sections/showcase-feedback").then((m) => ({ default: m.ShowcaseFeedback })),
|
|
23
|
+
);
|
|
24
|
+
const ShowcaseForms = React.lazy(() =>
|
|
25
|
+
import("./sections/showcase-forms").then((m) => ({ default: m.ShowcaseForms })),
|
|
26
|
+
);
|
|
27
|
+
const ShowcaseFoundations = React.lazy(() =>
|
|
28
|
+
import("./sections/showcase-foundations").then((m) => ({ default: m.ShowcaseFoundations })),
|
|
29
|
+
);
|
|
30
|
+
const ShowcaseGuidelinesRecipes = React.lazy(() =>
|
|
31
|
+
import("./sections/showcase-guidelines-recipes").then((m) => ({
|
|
32
|
+
default: m.ShowcaseGuidelinesRecipes,
|
|
33
|
+
})),
|
|
34
|
+
);
|
|
35
|
+
const ShowcaseOverlaysNavigation = React.lazy(() =>
|
|
36
|
+
import("./sections/showcase-overlays-navigation").then((m) => ({
|
|
37
|
+
default: m.ShowcaseOverlaysNavigation,
|
|
38
|
+
})),
|
|
39
|
+
);
|
|
40
|
+
const ShowcasePrimitives = React.lazy(() =>
|
|
41
|
+
import("./sections/showcase-primitives").then((m) => ({ default: m.ShowcasePrimitives })),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function SectionLoading() {
|
|
45
|
+
return <div className="h-96 animate-pulse rounded-xl bg-muted/40" aria-busy="true" />;
|
|
46
|
+
}
|
|
23
47
|
|
|
24
48
|
export function DesignSystemShowcase({
|
|
25
49
|
links,
|
|
@@ -32,10 +56,13 @@ export function DesignSystemShowcase({
|
|
|
32
56
|
|
|
33
57
|
/** สลับแท็บแล้วเลื่อนไปหัวข้อ — เลื่อนใน effect หลังแท็บใหม่ render แล้ว (ตำแหน่งจึงถูก) ชดเชย header ที่ sticky */
|
|
34
58
|
const [pendingAnchor, setPendingAnchor] = React.useState<string | null>(null);
|
|
59
|
+
const tries = React.useRef(0);
|
|
60
|
+
const [retry, setRetry] = React.useState(0);
|
|
35
61
|
const jumpTo = React.useCallback((tab: string, anchor?: string) => {
|
|
36
62
|
setActiveTab(tab);
|
|
37
63
|
setPendingAnchor(anchor ?? "__top__");
|
|
38
64
|
}, []);
|
|
65
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: retry ใช้เป็นตัวกระตุ้นให้ลองหาหัวข้อซ้ำระหว่างรอแท็บ lazy
|
|
39
66
|
React.useEffect(() => {
|
|
40
67
|
if (!pendingAnchor) return;
|
|
41
68
|
// หน่วงเล็กน้อยให้ Tabs โฟกัส trigger ใหม่เสร็จก่อน (การโฟกัสเลื่อนหน้าไปที่แถบแท็บ) แล้วค่อยเลื่อนไปหัวข้อ
|
|
@@ -47,15 +74,22 @@ export function DesignSystemShowcase({
|
|
|
47
74
|
}
|
|
48
75
|
const el = document.getElementById(pendingAnchor);
|
|
49
76
|
if (!el) {
|
|
77
|
+
// แท็บที่โหลดแบบ lazy อาจยัง render ไม่เสร็จ — ลองใหม่ได้ถึง ~2 วินาที
|
|
78
|
+
if (tries.current++ < 25) {
|
|
79
|
+
setRetry((r) => r + 1);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
tries.current = 0;
|
|
50
83
|
setPendingAnchor(null);
|
|
51
84
|
return;
|
|
52
85
|
}
|
|
86
|
+
tries.current = 0;
|
|
53
87
|
el.style.scrollMarginTop = "88px"; // ชดเชย header ที่ sticky
|
|
54
88
|
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
55
89
|
setPendingAnchor(null);
|
|
56
90
|
}, 80);
|
|
57
91
|
return () => window.clearTimeout(timer);
|
|
58
|
-
}, [pendingAnchor]);
|
|
92
|
+
}, [pendingAnchor, retry]);
|
|
59
93
|
|
|
60
94
|
const tabItems = [
|
|
61
95
|
{ value: "overview", label: "ภาพรวม", icon: LayoutGrid },
|
|
@@ -109,31 +143,45 @@ export function DesignSystemShowcase({
|
|
|
109
143
|
</TabsContent>
|
|
110
144
|
|
|
111
145
|
<TabsContent value="foundations" className="mt-0 outline-none">
|
|
112
|
-
<
|
|
146
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
147
|
+
<ShowcaseFoundations />
|
|
148
|
+
</React.Suspense>
|
|
113
149
|
</TabsContent>
|
|
114
150
|
|
|
115
151
|
<TabsContent value="primitives" className="mt-0 outline-none">
|
|
116
|
-
<
|
|
152
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
153
|
+
<ShowcasePrimitives />
|
|
154
|
+
</React.Suspense>
|
|
117
155
|
</TabsContent>
|
|
118
156
|
|
|
119
157
|
<TabsContent value="forms" className="mt-0 outline-none">
|
|
120
|
-
<
|
|
158
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
159
|
+
<ShowcaseForms />
|
|
160
|
+
</React.Suspense>
|
|
121
161
|
</TabsContent>
|
|
122
162
|
|
|
123
163
|
<TabsContent value="data-display" className="mt-0 outline-none">
|
|
124
|
-
<
|
|
164
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
165
|
+
<ShowcaseDataDisplay />
|
|
166
|
+
</React.Suspense>
|
|
125
167
|
</TabsContent>
|
|
126
168
|
|
|
127
169
|
<TabsContent value="overlays" className="mt-0 outline-none">
|
|
128
|
-
<
|
|
170
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
171
|
+
<ShowcaseOverlaysNavigation />
|
|
172
|
+
</React.Suspense>
|
|
129
173
|
</TabsContent>
|
|
130
174
|
|
|
131
175
|
<TabsContent value="feedback" className="mt-0 outline-none">
|
|
132
|
-
<
|
|
176
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
177
|
+
<ShowcaseFeedback />
|
|
178
|
+
</React.Suspense>
|
|
133
179
|
</TabsContent>
|
|
134
180
|
|
|
135
181
|
<TabsContent value="guidelines-recipes" className="mt-0 outline-none">
|
|
136
|
-
<
|
|
182
|
+
<React.Suspense fallback={<SectionLoading />}>
|
|
183
|
+
<ShowcaseGuidelinesRecipes />
|
|
184
|
+
</React.Suspense>
|
|
137
185
|
</TabsContent>
|
|
138
186
|
</Tabs>
|
|
139
187
|
</div>
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Button } from "@suphark/ui/components/ui/button";
|
|
4
|
-
import {
|
|
5
|
-
Tooltip,
|
|
6
|
-
TooltipContent,
|
|
7
|
-
TooltipProvider,
|
|
8
|
-
TooltipTrigger,
|
|
9
|
-
} from "@suphark/ui/components/ui/tooltip";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@suphark/ui/components/ui/tooltip";
|
|
10
5
|
import {
|
|
11
6
|
type CheckPermissionOptions,
|
|
12
7
|
type CheckPermissionRequirement,
|
|
@@ -118,14 +113,12 @@ export function ActionButton({
|
|
|
118
113
|
// 5. Wrap with Tooltip if provided or auto-generated
|
|
119
114
|
if (finalTooltip) {
|
|
120
115
|
return (
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
</Tooltip>
|
|
128
|
-
</TooltipProvider>
|
|
116
|
+
<Tooltip>
|
|
117
|
+
<TooltipTrigger delay={0} render={buttonContent as React.ReactElement} />
|
|
118
|
+
<TooltipContent side={tooltipSide}>
|
|
119
|
+
{typeof finalTooltip === "string" ? <p>{finalTooltip}</p> : finalTooltip}
|
|
120
|
+
</TooltipContent>
|
|
121
|
+
</Tooltip>
|
|
129
122
|
);
|
|
130
123
|
}
|
|
131
124
|
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// [FIX 1.1] นำเข้าเฉพาะ Button
|
|
4
4
|
import { Button } from "@suphark/ui/components/ui/button";
|
|
5
|
-
import {
|
|
6
|
-
Tooltip,
|
|
7
|
-
TooltipContent,
|
|
8
|
-
TooltipProvider,
|
|
9
|
-
TooltipTrigger,
|
|
10
|
-
} from "@suphark/ui/components/ui/tooltip";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@suphark/ui/components/ui/tooltip";
|
|
11
6
|
import {
|
|
12
7
|
type CheckPermissionOptions,
|
|
13
8
|
type CheckPermissionRequirement,
|
|
@@ -114,14 +109,10 @@ export function LinkButton({
|
|
|
114
109
|
// 3. Wrap with Tooltip if provided
|
|
115
110
|
if (tooltip) {
|
|
116
111
|
return (
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
{typeof tooltip === "string" ? <p>{tooltip}</p> : tooltip}
|
|
122
|
-
</TooltipContent>
|
|
123
|
-
</Tooltip>
|
|
124
|
-
</TooltipProvider>
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger delay={0} render={buttonContent as React.ReactElement} />
|
|
114
|
+
<TooltipContent>{typeof tooltip === "string" ? <p>{tooltip}</p> : tooltip}</TooltipContent>
|
|
115
|
+
</Tooltip>
|
|
125
116
|
);
|
|
126
117
|
}
|
|
127
118
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { NumberField } from "@base-ui/react/number-field";
|
|
4
4
|
import { cn } from "@suphark/ui/lib/utils";
|
|
5
5
|
import { MinusIcon, PlusIcon } from "lucide-react";
|
|
6
|
-
import type { ReactNode } from "react";
|
|
6
|
+
import type { ReactNode, Ref } from "react";
|
|
7
7
|
import { useUiText } from "../ui-text";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -17,7 +17,7 @@ function relaxHiddenStep(el: HTMLInputElement | null) {
|
|
|
17
17
|
|
|
18
18
|
const SIZE = {
|
|
19
19
|
sm: { group: "h-7", button: "w-7 [&_svg]:size-3.5", text: "text-sm" },
|
|
20
|
-
default: { group: "h-8", button: "w-8 [&_svg]:size-4", text: "text-
|
|
20
|
+
default: { group: "h-8", button: "w-8 [&_svg]:size-4", text: "text-body" },
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -53,6 +53,8 @@ export function NumberInput({
|
|
|
53
53
|
"aria-label": ariaLabel,
|
|
54
54
|
"aria-describedby": ariaDescribedBy,
|
|
55
55
|
"aria-invalid": ariaInvalid,
|
|
56
|
+
inputRef,
|
|
57
|
+
autoFocus,
|
|
56
58
|
}: {
|
|
57
59
|
value?: number | null;
|
|
58
60
|
defaultValue?: number;
|
|
@@ -84,6 +86,9 @@ export function NumberInput({
|
|
|
84
86
|
"aria-label"?: string;
|
|
85
87
|
"aria-describedby"?: string;
|
|
86
88
|
"aria-invalid"?: boolean | "true" | "false";
|
|
89
|
+
/** ref ของช่องที่ผู้ใช้พิมพ์ (เช่น focus() เอง) */
|
|
90
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
91
|
+
autoFocus?: boolean;
|
|
87
92
|
}) {
|
|
88
93
|
const text = useUiText().number;
|
|
89
94
|
const s = SIZE[size];
|
|
@@ -128,6 +133,8 @@ export function NumberInput({
|
|
|
128
133
|
)}
|
|
129
134
|
{prefix && <span className={cn(affix, !hideButtons && "pr-0")}>{prefix}</span>}
|
|
130
135
|
<NumberField.Input
|
|
136
|
+
ref={inputRef}
|
|
137
|
+
autoFocus={autoFocus}
|
|
131
138
|
placeholder={placeholder}
|
|
132
139
|
aria-label={ariaLabel}
|
|
133
140
|
aria-describedby={ariaDescribedBy}
|
|
@@ -184,7 +184,7 @@ export function TagsInput({
|
|
|
184
184
|
onBlur={() => {
|
|
185
185
|
if (draft.trim()) setDraft(add([draft]));
|
|
186
186
|
}}
|
|
187
|
-
className="h-6 min-w-24 flex-1 bg-transparent px-1 text-
|
|
187
|
+
className="h-6 min-w-24 flex-1 bg-transparent px-1 text-body outline-none placeholder:text-muted-foreground"
|
|
188
188
|
/>
|
|
189
189
|
</div>
|
|
190
190
|
{error && (
|
|
@@ -45,6 +45,13 @@ export function parseTimeInput(input: string): TimeValue | null {
|
|
|
45
45
|
return `${pad(h)}:${pad(m)}`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** ปัดนาทีลงให้ลงตัวกับ step (step 1 = ไม่เปลี่ยน) */
|
|
49
|
+
function roundTime(value: TimeValue | null, step: number): TimeValue | null {
|
|
50
|
+
if (!value || step <= 1) return value;
|
|
51
|
+
const [h, m] = value.split(":").map(Number);
|
|
52
|
+
return `${pad(h ?? 0)}:${pad(Math.floor((m ?? 0) / step) * step)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
export function splitTime(value: TimeValue | null | undefined): [number, number] | null {
|
|
49
56
|
const parsed = value ? parseTimeInput(value) : null;
|
|
50
57
|
if (!parsed) return null;
|
|
@@ -192,6 +199,7 @@ export function TimePicker({
|
|
|
192
199
|
defaultValue = null,
|
|
193
200
|
onValueChange,
|
|
194
201
|
minuteStep = 5,
|
|
202
|
+
roundToStep = false,
|
|
195
203
|
name,
|
|
196
204
|
id,
|
|
197
205
|
required,
|
|
@@ -206,6 +214,8 @@ export function TimePicker({
|
|
|
206
214
|
defaultValue?: TimeValue | null;
|
|
207
215
|
onValueChange?: (value: TimeValue | null) => void;
|
|
208
216
|
minuteStep?: number;
|
|
217
|
+
/** ปัดนาทีที่พิมพ์ลงตาม minuteStep (เช่น step 60 + พิมพ์ 14:30 → 14:00) — ค่าเริ่มต้นเก็บตามที่พิมพ์ */
|
|
218
|
+
roundToStep?: boolean;
|
|
209
219
|
name?: string;
|
|
210
220
|
id?: string;
|
|
211
221
|
required?: boolean;
|
|
@@ -238,7 +248,7 @@ export function TimePicker({
|
|
|
238
248
|
setDraft(null);
|
|
239
249
|
return;
|
|
240
250
|
}
|
|
241
|
-
const parsed = parseTimeInput(draft);
|
|
251
|
+
const parsed = roundTime(parseTimeInput(draft), roundToStep ? minuteStep : 1);
|
|
242
252
|
if (parsed) {
|
|
243
253
|
setTime(parsed);
|
|
244
254
|
setDraft(null);
|
|
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
|
8
8
|
type={type}
|
|
9
9
|
data-slot="input"
|
|
10
10
|
className={cn(
|
|
11
|
-
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-
|
|
11
|
+
"h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-body transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
12
12
|
className,
|
|
13
13
|
)}
|
|
14
14
|
{...props}
|
|
@@ -19,7 +19,7 @@ function NativeSelect({ className, size = "default", ...props }: NativeSelectPro
|
|
|
19
19
|
<select
|
|
20
20
|
data-slot="native-select"
|
|
21
21
|
data-size={size}
|
|
22
|
-
className="h-8 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 pr-8 pl-2.5 text-
|
|
22
|
+
className="h-8 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 pr-8 pl-2.5 text-body transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
|
|
23
23
|
{...props}
|
|
24
24
|
/>
|
|
25
25
|
<ChevronDownIcon
|
|
@@ -40,7 +40,7 @@ function SelectTrigger({
|
|
|
40
40
|
data-slot="select-trigger"
|
|
41
41
|
data-size={size}
|
|
42
42
|
className={cn(
|
|
43
|
-
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-
|
|
43
|
+
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-body whitespace-nowrap transition-colors outline-none select-none 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-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *: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:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
44
44
|
className,
|
|
45
45
|
)}
|
|
46
46
|
{...props}
|
|
@@ -6,7 +6,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
6
6
|
<textarea
|
|
7
7
|
data-slot="textarea"
|
|
8
8
|
className={cn(
|
|
9
|
-
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-
|
|
9
|
+
"flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-body transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
10
10
|
className,
|
|
11
11
|
)}
|
|
12
12
|
{...props}
|