@suphark/ui 0.4.2 → 0.4.3

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,14 @@
2
2
 
3
3
  รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
4
4
 
5
+ ## [0.4.3] — 2026-09-19
6
+
7
+ ### Added
8
+
9
+ - **`useConfirm()`** (barrel) — `window.confirm` แบบ promise บน ConfirmDialog สำหรับโค้ดฝั่ง client ที่ไม่ใช่ server action (ยกมาจาก DueBell ที่ย้าย window.confirm 8 จุดด้วย hook นี้): `const { confirm, confirmDialog } = useConfirm(); if (!(await confirm({ description, destructive: true }))) return;` แล้ว render `{confirmDialog}`; เรียกซ้ำขณะเปิดอยู่ → คำขอเดิม resolve(false)
10
+ - `ConfirmSpec` object รับ `destructive?` (RowActions/ConfirmActionButton ใช้เป็นตัวทับค่าเดิมของรายการ/variant)
11
+ - README: ตาราง "ยืนยันก่อนทำ — ห้ามใช้ window.confirm" (เลือก RowActions.confirm / ConfirmActionButton / useConfirm / ConfirmDialog) และหมายเหตุ Turbopack ค้าง `Package path ./styles.css is exported … but no valid target file` หลังอัปเดตแพ็กเกจ = cache ของ dev server ให้ลบ `.next`
12
+
5
13
  ## [0.4.2] — 2026-09-19
6
14
 
7
15
  ### Added
package/README.md CHANGED
@@ -51,11 +51,15 @@ import { ThemeProvider, Toaster } from "@suphark/ui";
51
51
 
52
52
  แอปจะ type-check source ของแพ็กเกจด้วย — แพ็กเกจผ่าน `strict` + `noUncheckedIndexedAccess` + `verbatimModuleSyntax` อยู่แล้ว
53
53
 
54
+ ### หลังอัปเดตเวอร์ชันแพ็กเกจ
55
+
56
+ ถ้า Turbopack ค้าง `Package path ./styles.css is exported … but no valid target file` (หรือ resolve entry อื่นไม่ได้) หลัง `pnpm install` เวอร์ชันใหม่ — เป็น cache ของ dev server ไม่ใช่ปัญหาแพ็กเกจ: หยุด dev server แล้วลบ `.next` ของแอป
57
+
54
58
  ## การ import
55
59
 
56
60
  ```ts
57
61
  // ทุกอย่างที่ไม่พึ่ง Next — primitives, shared, theme, hooks, cn
58
- import { Button, Card, FormFieldInput, StatusBadge, ThemeToggle, cn } from "@suphark/ui";
62
+ import { Button, Card, FormFieldInput, StatusBadge, ThemeToggle, ConfirmDialog, useConfirm, cn } from "@suphark/ui";
59
63
 
60
64
  // ของที่ใช้ next/navigation หรือ next/link + โครงแอป
61
65
  import { AppShell, UserMenu, filterNav, FormDialog, RowActions, LinkButton, RefreshButton, PageShell } from "@suphark/ui/next";
@@ -140,6 +144,19 @@ export default async function Layout({ children }) {
140
144
  <div style={{ "--brand": tenant.color, "--brand-foreground": "oklch(0.985 0 0)" } as React.CSSProperties}>…</div>
141
145
  ```
142
146
 
147
+ ### ยืนยันก่อนทำ — ห้ามใช้ `window.confirm`
148
+
149
+ webview/automation บางตัวปิด `window.confirm` ทิ้ง (กดแล้วไม่เกิดอะไร) และไม่เข้า design system — ใช้ 3 ตัวนี้แทน
150
+
151
+ | สถานการณ์ | ใช้ |
152
+ | --- | --- |
153
+ | รายการในเมนู … ท้ายแถว | `RowActions` / `RowMenu` prop `confirm` (string หรือ `{ title, description, confirmLabel }`) |
154
+ | ปุ่มเดี่ยวเรียก server action | `ConfirmActionButton` (`@suphark/ui/next`) — `confirm` + `action` + `data` |
155
+ | โค้ด client อื่น ๆ (fetch/mutation เอง) | `const { confirm, confirmDialog } = useConfirm(); if (!(await confirm({ description, destructive: true }))) return;` แล้ว render `{confirmDialog}` |
156
+ | คุม state เอง | `ConfirmDialog` controlled |
157
+
158
+ ข้อความค่าเริ่มต้น (หัว/ปุ่ม) มาจาก `UiText.confirmTitle` / `UiText.confirm`
159
+
143
160
  ### สิทธิ์ (ActionButton/LinkButton `permission` prop)
144
161
 
145
162
  แพ็กเกจไม่รู้จักระบบสิทธิ์ของแอป — ครอบด้วย `UiPermissionProvider` แล้วส่งฟังก์ชัน `check(requirement, options)` ของแอปเข้ามา ถ้าไม่ครอบ = อนุญาตทุกอย่าง
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suphark/ui",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
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 เอง",
@@ -36,7 +36,7 @@ export function ConfirmActionButton({
36
36
  const [open, setOpen] = useState(false);
37
37
  const [pending, start] = useTransition();
38
38
  const spec = normalizeConfirm(confirm);
39
- const destructive = variant === "destructive";
39
+ const destructive = spec.destructive ?? variant === "destructive";
40
40
  return (
41
41
  <>
42
42
  <ActionButton
@@ -22,6 +22,8 @@ export type ConfirmSpec =
22
22
  description: ReactNode;
23
23
  confirmLabel?: string;
24
24
  cancelLabel?: string;
25
+ /** ปุ่มยืนยันสีแดง (RowActions ใช้ `destructive` ของรายการเป็นค่าเริ่มต้นอยู่แล้ว) */
26
+ destructive?: boolean;
25
27
  };
26
28
 
27
29
  /** แปลง ConfirmSpec เป็น object เสมอ */
@@ -107,7 +107,7 @@ export function RowActions({ items, label }: { items: RowAction[]; label?: strin
107
107
  description={spec.description}
108
108
  confirmLabel={spec.confirmLabel}
109
109
  cancelLabel={spec.cancelLabel}
110
- destructive={confirming.destructive}
110
+ destructive={spec.destructive ?? confirming.destructive}
111
111
  onConfirm={() => {
112
112
  const it = confirming;
113
113
  setConfirming(null);
@@ -0,0 +1,63 @@
1
+ "use client";
2
+
3
+ import {
4
+ ConfirmDialog,
5
+ type ConfirmSpec,
6
+ normalizeConfirm,
7
+ } from "@suphark/ui/components/shared/confirm-dialog";
8
+ import { type ReactNode, useCallback, useState } from "react";
9
+
10
+ /**
11
+ * `window.confirm` แบบ promise บน ConfirmDialog — สำหรับโค้ดฝั่ง client ที่ไม่ใช่ server action
12
+ * (เช่น กดลบแล้วเรียก fetch/mutation เอง) — ยกมาจาก DueBell
13
+ *
14
+ * const { confirm, confirmDialog } = useConfirm();
15
+ * async function remove() {
16
+ * if (!(await confirm({ description: "ลบรายการนี้?", destructive: true }))) return;
17
+ * …
18
+ * }
19
+ * return <>{confirmDialog}<Button onClick={remove}>ลบ</Button></>;
20
+ *
21
+ * ต้อง render `confirmDialog` ในต้นไม้ของ component ที่เรียก hook (ครั้งเดียวต่อ hook)
22
+ * เรียก `confirm` ซ้ำขณะกล่องเปิดอยู่ → คำขอเดิม resolve(false) แล้วแสดงคำขอใหม่แทน
23
+ */
24
+ export function useConfirm(): {
25
+ confirm: (spec: ConfirmSpec) => Promise<boolean>;
26
+ confirmDialog: ReactNode;
27
+ } {
28
+ const [request, setRequest] = useState<{
29
+ options: Exclude<ConfirmSpec, string>;
30
+ resolve: (ok: boolean) => void;
31
+ } | null>(null);
32
+
33
+ const confirm = useCallback(
34
+ (spec: ConfirmSpec) =>
35
+ new Promise<boolean>((resolve) => {
36
+ setRequest((prev) => {
37
+ prev?.resolve(false);
38
+ return { options: normalizeConfirm(spec), resolve };
39
+ });
40
+ }),
41
+ [],
42
+ );
43
+
44
+ function settle(ok: boolean) {
45
+ request?.resolve(ok);
46
+ setRequest(null);
47
+ }
48
+
49
+ const confirmDialog = request ? (
50
+ <ConfirmDialog
51
+ open
52
+ onOpenChange={(open) => !open && settle(false)}
53
+ title={request.options.title}
54
+ description={request.options.description}
55
+ confirmLabel={request.options.confirmLabel}
56
+ cancelLabel={request.options.cancelLabel}
57
+ destructive={request.options.destructive}
58
+ onConfirm={() => settle(true)}
59
+ />
60
+ ) : null;
61
+
62
+ return { confirm, confirmDialog };
63
+ }
package/src/index.ts CHANGED
@@ -142,6 +142,7 @@ export * from "./components/ui/toggle";
142
142
  export * from "./components/ui/toggle-group";
143
143
  export * from "./components/ui/tooltip";
144
144
  // ---------- lib / hooks ----------
145
+ export { useConfirm } from "./hooks/use-confirm";
145
146
  export { useCopyToClipboard } from "./hooks/use-copy-to-clipboard";
146
147
  export { useIsMobile } from "./hooks/use-mobile";
147
148
  export { copyText, selectContents } from "./lib/copy-text";