@suphark/ui 0.31.0 → 0.33.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 +28 -0
- package/package.json +2 -2
- package/src/components/design-system/sections/showcase-foundations.tsx +1 -1
- package/src/components/shared/activity-feed.tsx +13 -7
- package/src/components/shared/count-badge.tsx +1 -1
- package/src/components/shared/time/client-date-formatter.tsx +6 -2
- package/src/components/shared/ui-text.tsx +4 -0
- package/src/components/sortable/sortable-list.tsx +7 -1
- package/src/styles.css +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
|
|
4
4
|
|
|
5
|
+
## [0.33.0] — 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **คอนทราสต์ไม่ผ่าน WCAG AA 3 จุด** (DueBell วัดจากหน้าจริงทั้งสองธีม — ผมคำนวณจาก token ซ้ำได้ตัวเลขตรงกันทุกจุด):
|
|
10
|
+
- `--info` ธีมสว่าง: `text-info` บนพื้นขาว **4.02:1** → ลดความสว่าง L 0.588 → 0.53 = **5.05:1** (ตัวหนังสือขาวบน `bg-info` ก็ขึ้นเป็น 5.05:1 เท่ากัน) — สีฟ้าเข้มขึ้นเล็กน้อย
|
|
11
|
+
- `--muted-foreground` ธีมสว่าง: บนพื้น `--muted` **4.35:1** (กระทบ `CountBadge tone="muted"`, แท็บที่ไม่ได้เลือกบน `TabsList`, ป้ายนับใน `SegmentedControl` …) → L 0.554 → 0.52 = **5.02:1** (บนขาว 5.4:1) — ตัวหนังสือรองเข้มขึ้นเล็กน้อยทั้งระบบ
|
|
12
|
+
- ป้ายนับ `CountBadge tone="destructive"` (รวมปุ่มแจ้งเตือนใน `AppShell`/`MobileTabBar`) ธีมมืด: ตัวหนังสือขาวบน `--destructive` **2.89:1** → token ใหม่ **`--destructive-foreground`** (สว่าง = ขาว 4.76:1, มืด = สีพื้นเข้ม **6.97:1**) — ไม่ลดความสว่างของ `--destructive` ธีมมืดเพราะ `text-destructive` บนพื้นมืดจะไม่ผ่านแทน
|
|
13
|
+
- ใช้ `text-destructive-foreground` คู่ `bg-destructive` ได้ทุกที่ (ใหม่ใน `@theme`)
|
|
14
|
+
|
|
15
|
+
## [0.32.0] — 2026-10-06
|
|
16
|
+
|
|
17
|
+
> แก้บั๊กที่แอปรายงานหลัง 0.30.0 — เดิมเตรียมไว้ใน 0.31.0 แต่ 0.31.0 ถูก publish (12:53 น.) ก่อน commit ทั้งสามนี้ (12:56–13:09 น.) ตรวจ tarball แล้วไม่มี จึงออกเป็น 0.32.0
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`@suphark/ui/sortable`: แถวกลุ่มไม่ได้อยู่ใน `SortableContext`** (รายงานจาก Pi Config ตอนย้าย `values-table`) — `SortableGroup` สร้าง context ให้เฉพาะรายการในกลุ่ม ส่วนแถวกลุ่มที่ลงทะเบียนด้วย `useSortableRow({ type: "group" })` ไม่มี context ไหนรับ ทำให้ลากกลุ่มแล้วไม่มีการเลื่อนแถวให้เห็นระหว่างลาก (ปล่อยแล้วลำดับยังถูก) — ตอนนี้ `SortableProvider` สร้าง `SortableContext` ชั้นนอกจาก `order.groups` ให้เอง **ไม่ต้องแก้ call site และลำดับใน context หลุดจากลำดับจริงไม่ได้**
|
|
22
|
+
|
|
23
|
+
- **`ActivityFeed relative={false}` พังทุกแถว** (รายงานจาก Pi Auth): วันที่กลายเป็น `-` และ console เต็มไปด้วย `TypeError: Invalid option` เพราะส่ง `timeStyle` ปนกับ `day`/`month`/`year` ซึ่ง `Intl.DateTimeFormat` ไม่ยอม — ต้นเหตุจริงอยู่ที่ **`ClientDateFormatter` เอา default (`day`/`month`/`year`) ไปผสมกับ options ของผู้เรียกเสมอ** ทำให้ทุกแอปที่ส่ง `dateStyle`/`timeStyle` พังเหมือนกัน; ตอนนี้ถ้าผู้เรียกใช้ `dateStyle`/`timeStyle` จะไม่ผสม default ให้ และ `ActivityFeed` เปลี่ยนไปใช้ `{ dateStyle: "medium", timeStyle: "short" }` (เทสต์ใหม่ครอบทั้งสองชั้น)
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `prepublishOnly` เรียก `scripts/release-check.mjs` ก่อน publish: working tree ต้องสะอาด, HEAD ต้องตรงกับ `origin/main` หลัง fetch และ CHANGELOG ต้องมีหัวข้อของเวอร์ชันนั้น — กันปล่อยรุ่นที่ขาด commit (เกิดมาแล้ว 3 ครั้ง: 0.17.0, 0.30.0, 0.31.0; ข้อเสนอจาก Pi Auth/Pi Config)
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- `ActivityFeed`: รับ `locale` แล้วส่งต่อให้ตัวฟอร์แมตวันที่ (ไม่ส่ง = `th-TH` เหมือนเดิม) — เดิมวันที่เป็นไทยตายตัวสำหรับทุกคน แอปสองภาษาจึงแสดงผิดเมื่อผู้ใช้เลือกอังกฤษ (รายงานจาก Pi Auth ตอนทำหน้า audit log); `emptyLabel` ค่าเริ่มต้นย้ายไปอ่านจาก `UiText.activity.empty` (คีย์ใหม่)
|
|
32
|
+
|
|
5
33
|
## [0.31.0] — 2026-10-06
|
|
6
34
|
|
|
7
35
|
> 0.30.0 ถูก publish ก่อน commit ของ `NotificationCenter` (ตรวจ tarball แล้วไม่มีไฟล์ `shared/notification-center.tsx`) จึงเลื่อนมาออกเป็น 0.31.0 — ของอื่นใน 0.30.0 อยู่ครบตามรายการข้างล่าง
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.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 เอง",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"check:typography": "node scripts/audit-tailwind-typography.mjs",
|
|
65
65
|
"verify": "pnpm typecheck && pnpm check:barrel && pnpm lint:base-ui && pnpm check:typography && pnpm test",
|
|
66
66
|
"check:barrel": "node ../../scripts/barrel-exports.mjs",
|
|
67
|
-
"prepublishOnly": "node ../../scripts/barrel-exports.mjs && tsc --noEmit"
|
|
67
|
+
"prepublishOnly": "node ../../scripts/release-check.mjs && node ../../scripts/barrel-exports.mjs && tsc --noEmit"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -60,7 +60,7 @@ export function ShowcaseFoundations() {
|
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
name: "Destructive",
|
|
63
|
-
class: "bg-destructive text-
|
|
63
|
+
class: "bg-destructive text-destructive-foreground",
|
|
64
64
|
token: "var(--destructive)",
|
|
65
65
|
tailwind: "bg-destructive / text-destructive",
|
|
66
66
|
usage: "Irreversible actions, Delete confirmation, Error callouts",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Initials } from "@suphark/ui/components/shared/initials";
|
|
4
4
|
import { ClientDateFormatter } from "@suphark/ui/components/shared/time/client-date-formatter";
|
|
5
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
5
6
|
import { cn } from "@suphark/ui/lib/utils";
|
|
6
7
|
import type { ReactNode } from "react";
|
|
7
8
|
|
|
@@ -43,20 +44,27 @@ const DOT_TONE: Record<ActivityTone, string> = {
|
|
|
43
44
|
export function ActivityFeed({
|
|
44
45
|
items,
|
|
45
46
|
relative = true,
|
|
47
|
+
locale,
|
|
46
48
|
emptyLabel,
|
|
47
49
|
className,
|
|
48
50
|
}: {
|
|
49
51
|
items: ReadonlyArray<ActivityItem>;
|
|
50
52
|
/** true = "5 นาทีที่แล้ว" (ค่าเริ่มต้น) · false = วันที่/เวลาเต็ม */
|
|
51
53
|
relative?: boolean;
|
|
52
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* ภาษาของวันที่ เช่น `"en-US"` หรือ `"th-TH-u-ca-gregory"` — ไม่ส่ง = `th-TH` (พ.ศ.)
|
|
56
|
+
* แอปสองภาษาต้องส่งตาม locale ของผู้ใช้ ไม่งั้นวันที่จะเป็นไทยสำหรับทุกคน
|
|
57
|
+
*/
|
|
58
|
+
locale?: string;
|
|
59
|
+
/** ไม่มีรายการ (ไม่ส่ง = `UiText.activity.empty`) */
|
|
53
60
|
emptyLabel?: ReactNode;
|
|
54
61
|
className?: string;
|
|
55
62
|
}) {
|
|
63
|
+
const text = useUiText();
|
|
56
64
|
if (items.length === 0) {
|
|
57
65
|
return (
|
|
58
66
|
<p className={cn("py-6 text-center text-muted-foreground text-sm", className)}>
|
|
59
|
-
{emptyLabel ?? "ยังไม่มีรายการ"}
|
|
67
|
+
{emptyLabel ?? text.activity?.empty ?? "ยังไม่มีรายการ"}
|
|
60
68
|
</p>
|
|
61
69
|
);
|
|
62
70
|
}
|
|
@@ -95,12 +103,10 @@ export function ActivityFeed({
|
|
|
95
103
|
)}
|
|
96
104
|
<ClientDateFormatter
|
|
97
105
|
date={item.at}
|
|
106
|
+
locale={locale}
|
|
98
107
|
formatType={relative ? "relative" : "standard"}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
? undefined
|
|
102
|
-
: { day: "numeric", month: "short", year: "numeric", timeStyle: "short" }
|
|
103
|
-
}
|
|
108
|
+
// Intl ห้ามใช้ dateStyle/timeStyle ปนกับ field รายตัว (day/month/year) — โยน TypeError
|
|
109
|
+
options={relative ? undefined : { dateStyle: "medium", timeStyle: "short" }}
|
|
104
110
|
className="text-muted-foreground text-xs"
|
|
105
111
|
/>
|
|
106
112
|
</div>
|
|
@@ -29,7 +29,7 @@ export function CountBadge({
|
|
|
29
29
|
tone === "primary"
|
|
30
30
|
? "bg-primary text-primary-foreground"
|
|
31
31
|
: tone === "destructive"
|
|
32
|
-
? "bg-destructive text-
|
|
32
|
+
? "bg-destructive text-destructive-foreground"
|
|
33
33
|
: "bg-muted text-muted-foreground",
|
|
34
34
|
className,
|
|
35
35
|
)}
|
|
@@ -66,10 +66,14 @@ export function ClientDateFormatter({
|
|
|
66
66
|
if (formatType === "relative") {
|
|
67
67
|
return formatDistance(dateObj, now, { addSuffix: true, locale: getLocaleObject(locale) });
|
|
68
68
|
}
|
|
69
|
+
const user: Intl.DateTimeFormatOptions = optionsKey ? JSON.parse(optionsKey) : {};
|
|
70
|
+
// Intl ห้าม dateStyle/timeStyle ปนกับ field รายตัว (day/month/year/hour…) — โยน TypeError
|
|
71
|
+
// ผู้เรียกที่ใช้สไตล์จึงต้องไม่ถูกยัด DEFAULT_OPTIONS (day/month/year) เข้าไปด้วย
|
|
72
|
+
const usesStyle = user.dateStyle !== undefined || user.timeStyle !== undefined;
|
|
69
73
|
const opts: Intl.DateTimeFormatOptions = {
|
|
70
|
-
...DEFAULT_OPTIONS,
|
|
74
|
+
...(usesStyle ? {} : DEFAULT_OPTIONS),
|
|
71
75
|
timeZone,
|
|
72
|
-
...
|
|
76
|
+
...user,
|
|
73
77
|
};
|
|
74
78
|
return getDateTimeFormat(locale, opts).format(dateObj);
|
|
75
79
|
} catch (error) {
|
|
@@ -65,6 +65,8 @@ export interface UiText {
|
|
|
65
65
|
}>;
|
|
66
66
|
/** ErrorState เซสชันหมดอายุ (optional) */
|
|
67
67
|
sessionExpired?: Partial<{ title: string; description: string; signIn: string }>;
|
|
68
|
+
/** ActivityFeed (optional) */
|
|
69
|
+
activity?: Partial<{ empty: string }>;
|
|
68
70
|
/** NotificationCenter (optional) */
|
|
69
71
|
notifications?: Partial<{ title: string; empty: string; viewAll: string }>;
|
|
70
72
|
/** SelectionBar (optional) — `selected` ใช้ `{count}` */
|
|
@@ -239,6 +241,7 @@ export const UI_TEXT_TH: UiText = {
|
|
|
239
241
|
"คุณถูกออกจากระบบระหว่างใช้งาน ข้อมูลที่ยังไม่ได้บันทึกจะหายไป กรุณาเข้าสู่ระบบอีกครั้ง แล้วระบบจะพากลับมาหน้านี้",
|
|
240
242
|
signIn: "เข้าสู่ระบบ",
|
|
241
243
|
},
|
|
244
|
+
activity: { empty: "ยังไม่มีรายการ" },
|
|
242
245
|
notifications: {
|
|
243
246
|
title: "การแจ้งเตือน",
|
|
244
247
|
empty: "ไม่มีรายการที่ต้องจัดการ",
|
|
@@ -403,6 +406,7 @@ export const UI_TEXT_EN: UiText = {
|
|
|
403
406
|
"You were signed out while working. Unsaved changes are lost — sign in again and we will bring you back to this page.",
|
|
404
407
|
signIn: "Sign in",
|
|
405
408
|
},
|
|
409
|
+
activity: { empty: "No activity yet" },
|
|
406
410
|
notifications: {
|
|
407
411
|
title: "Notifications",
|
|
408
412
|
empty: "Nothing needs your attention",
|
|
@@ -310,6 +310,8 @@ export function SortableProvider({
|
|
|
310
310
|
};
|
|
311
311
|
}, [announcements, describe, text.sortable]);
|
|
312
312
|
|
|
313
|
+
const groupIds = useMemo(() => order.groups.map((id) => dndId("group", id)), [order.groups]);
|
|
314
|
+
|
|
313
315
|
const state = useMemo<SortableState>(
|
|
314
316
|
() => ({
|
|
315
317
|
dragging,
|
|
@@ -333,7 +335,11 @@ export function SortableProvider({
|
|
|
333
335
|
onDragEnd={onDragEnd}
|
|
334
336
|
onDragCancel={onDragCancel}
|
|
335
337
|
>
|
|
336
|
-
{
|
|
338
|
+
{/* ชั้นนอก = ลำดับของกลุ่ม (provider รู้ `order.groups` อยู่แล้ว แอปจึงไม่ต้องสร้างเอง
|
|
339
|
+
และลำดับใน context หลุดจากลำดับจริงไม่ได้) · ชั้นใน = `SortableGroup` ของแต่ละกลุ่ม */}
|
|
340
|
+
<SortableContext items={groupIds} strategy={verticalListSortingStrategy}>
|
|
341
|
+
{children}
|
|
342
|
+
</SortableContext>
|
|
337
343
|
</DndContext>
|
|
338
344
|
</SortableStateContext.Provider>
|
|
339
345
|
);
|
package/src/styles.css
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
--color-input: var(--input);
|
|
46
46
|
--color-border: var(--border);
|
|
47
47
|
--color-destructive: var(--destructive);
|
|
48
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
48
49
|
--color-success: var(--success);
|
|
49
50
|
--color-success-foreground: var(--success-foreground);
|
|
50
51
|
--color-warning: var(--warning);
|
|
@@ -148,17 +149,21 @@
|
|
|
148
149
|
--secondary: oklch(0.968 0.007 247.896);
|
|
149
150
|
--secondary-foreground: oklch(0.208 0.042 265.755);
|
|
150
151
|
--muted: oklch(0.968 0.007 247.896);
|
|
151
|
-
|
|
152
|
+
/* 0.554 → 0.52: บนพื้น --muted (แท็บ, ป้ายนับ, ช่อง disabled) เดิมได้ 4.35:1 ไม่ผ่าน AA → 5.02:1 (บนขาว 5.4:1) */
|
|
153
|
+
--muted-foreground: oklch(0.52 0.046 257.417);
|
|
152
154
|
--accent: oklch(0.968 0.007 247.896);
|
|
153
155
|
--accent-foreground: oklch(0.208 0.042 265.755);
|
|
154
156
|
--destructive: oklch(0.577 0.245 27.325);
|
|
157
|
+
/* ตัวหนังสือบนพื้น --destructive (ป้ายนับแจ้งเตือน) — ขาว 4.76:1 */
|
|
158
|
+
--destructive-foreground: oklch(1 0 0);
|
|
155
159
|
--success: oklch(0.527 0.154 150.069);
|
|
156
160
|
--success-foreground: oklch(0.985 0.002 247.839);
|
|
157
161
|
--warning: oklch(0.852 0.199 91.936);
|
|
158
162
|
--warning-foreground: oklch(0.286 0.066 53.813);
|
|
159
163
|
--warning-muted-foreground: oklch(0.421 0.095 57.708);
|
|
160
164
|
/* โทน: ค่าเท่า Tailwind sky/emerald/amber/red ที่ Notice/StatusBadge เคยใช้ตรง ๆ (50 พื้น · 200 ขอบ · 800 ตัวอักษร) */
|
|
161
|
-
|
|
165
|
+
/* 0.588 → 0.53: `text-info` บนขาวเดิม 4.02:1 ไม่ผ่าน AA → 5.05:1 (ตัวหนังสือขาวบน bg-info ก็ผ่านเท่ากัน) */
|
|
166
|
+
--info: oklch(0.53 0.158 241.966);
|
|
162
167
|
--info-foreground: oklch(0.984 0.003 247.858);
|
|
163
168
|
--info-subtle: oklch(0.977 0.013 236.62);
|
|
164
169
|
--info-subtle-foreground: oklch(0.443 0.11 240.79);
|
|
@@ -208,6 +213,9 @@
|
|
|
208
213
|
--accent: oklch(0.279 0.041 260.031);
|
|
209
214
|
--accent-foreground: oklch(0.984 0.003 247.858);
|
|
210
215
|
--destructive: oklch(0.704 0.191 22.216);
|
|
216
|
+
/* ขาวบน --destructive ธีมมืด = 2.89:1 ไม่ผ่าน — ใช้สีพื้นหลังมืดเป็นตัวหนังสือ = 6.97:1
|
|
217
|
+
(ไม่ลดความสว่างของ --destructive เพราะ text-destructive บนพื้นมืดจะไม่ผ่านแทน) */
|
|
218
|
+
--destructive-foreground: oklch(0.129 0.042 264.695);
|
|
211
219
|
--success: oklch(0.627 0.194 149.214);
|
|
212
220
|
--success-foreground: oklch(0.145 0.028 147.727);
|
|
213
221
|
--warning: oklch(0.852 0.199 91.936);
|