@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 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.25.1",
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
- import { ShowcasePrimitives } from "./sections/showcase-primitives";
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
- <ShowcaseFoundations />
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
- <ShowcasePrimitives />
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
- <ShowcaseForms />
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
- <ShowcaseDataDisplay />
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
- <ShowcaseOverlaysNavigation />
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
- <ShowcaseFeedback />
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
- <ShowcaseGuidelinesRecipes />
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
- <TooltipProvider>
122
- <Tooltip>
123
- <TooltipTrigger render={buttonContent as React.ReactElement} />
124
- <TooltipContent side={tooltipSide}>
125
- {typeof finalTooltip === "string" ? <p>{finalTooltip}</p> : finalTooltip}
126
- </TooltipContent>
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
- <TooltipProvider>
118
- <Tooltip>
119
- <TooltipTrigger render={buttonContent as React.ReactElement} />
120
- <TooltipContent>
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-base md:text-sm" },
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-base outline-none placeholder:text-muted-foreground md:text-sm"
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-base 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 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
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-sm 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"
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-sm 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",
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-base 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 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
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}