@suphark/ui 0.26.1 → 0.27.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 +15 -0
- package/README.md +27 -0
- package/package.json +1 -1
- package/src/components/app-shell/command-palette.tsx +1 -1
- package/src/components/data-table/data-table-pagination.tsx +7 -5
- package/src/components/data-table/data-table-toolbar.tsx +1 -1
- package/src/components/shared/action-menu.tsx +1 -1
- package/src/components/shared/auth-parts.tsx +1 -1
- package/src/components/shared/button/button.tsx +110 -0
- package/src/components/shared/button/index.ts +6 -0
- package/src/components/shared/form/time-picker.tsx +1 -1
- package/src/components/shared/month-stepper.tsx +1 -1
- package/src/components/shared/notice.tsx +1 -1
- package/src/components/shared/resource-property-filter.tsx +1 -1
- package/src/components/shared/row-actions.tsx +1 -1
- package/src/components/shared/rule-builder.tsx +1 -1
- package/src/components/shared/secret-reveal.tsx +1 -1
- package/src/components/shared/simple-dialog.tsx +1 -1
- package/src/components/shared/simple-pagination.tsx +1 -1
- package/src/components/shared/simple-tooltip.tsx +11 -6
- package/src/components/shared/theme/theme-switch.tsx +1 -1
- package/src/components/shared/theme/theme-toggle.tsx +5 -3
- package/src/components/shared/ui-text.tsx +25 -4
- package/src/components/shared/upload-list.tsx +1 -1
- package/src/index.ts +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) · semver: patch = หน้าตา/บั๊ก, minor = component/prop ใหม่, major = เปลี่ยน/ลบ props
|
|
4
4
|
|
|
5
|
+
## [0.27.0] — 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`Button`: tooltip อัตโนมัติสำหรับปุ่มไอคอน** — มาตรฐานใหม่ของทุกโครงการ (เจ้าของสั่ง 2026-10-05): ไอคอนที่ไม่มีข้อความกำกับต้องบอกได้ว่าคืออะไรเมื่อวางเมาส์ ถ้า `Button` มี `aria-label` และ children ไม่มีข้อความ (ไอคอน/`sr-only` ไม่นับ) จะห่อ `Tooltip` ให้เองจาก `aria-label` — ทำที่ `Button` ชั้นในสุดที่เดียว **call site ทุกที่ของทุกแอปได้ทันทีโดยไม่ต้องแก้โค้ด** รวมถึงปุ่มที่ส่งเข้า `render=`/`trigger` ของ `FormDialog`/`SimpleDialog`/`DropdownMenu`/`RowActions` (เคสที่ห่อ `SimpleTooltip` เองไม่ได้เพราะ trigger ส่ง ref/props ให้)
|
|
10
|
+
- props ใหม่: `tooltip` (ทับข้อความ, `false` = ปิด), `tooltipSide`, `tooltipDelay` — ไม่ต้องมี `TooltipProvider` ใน tree (ใส่ได้ถ้าอยากคุม delay ร่วมกัน)
|
|
11
|
+
- `SimpleTooltip` ปิด tooltip อัตโนมัติของ `Button` ที่ห่ออยู่ให้แล้ว (ไม่ขึ้นสองชั้น) — แอปที่ห่อ tooltip ด้วยกลไกอื่นใช้ `ButtonTooltipSuppress` (export ใหม่) หรือ `tooltip={false}`
|
|
12
|
+
- barrel export `Button` จาก `components/shared/button/button.tsx` (ครอบ `components/ui/button.tsx` ที่ยังเป็นไฟล์ shadcn ดิบ ไม่ถูกแก้มือ); `buttonVariants` เหมือนเดิม; props อื่นและหน้าตาไม่เปลี่ยน
|
|
13
|
+
- `DataTablePagination`: ปุ่มเลื่อนหน้าทั้ง 4 ใช้ `aria-label` จาก `UiText` (เดิมเป็นข้อความอังกฤษตายตัวใน `sr-only`); `ThemeToggle` ใช้ `aria-label` จาก `UiText.theme.label` แทน `title` (เลิก tooltip ซ้อนของเบราว์เซอร์)
|
|
14
|
+
- README หัวข้อ "ปุ่มไอคอน — tooltip มาให้เอง (มาตรฐานทุกโครงการ)" + แถวใหม่ใน `docs/agent-rules.md`
|
|
15
|
+
- ใช้ข้อความใน `<span className="sr-only">` (รูปแบบของ shadcn) เป็น tooltip ได้เมื่อไม่มี `aria-label`
|
|
16
|
+
- **ปุ่มไอคอนในแพ็กเกจเองได้ tooltip ครบ**: `RowActions`, `ActionMenu`, `SimpleDialog`/`SimpleSheet` (ปุ่มปิด), `SimplePagination`, `DataTablePagination`, `DataTableToolbar`, `CommandPalette`, `ThemeToggle`/`ThemeSwitch`, `Notice`, `UploadList`, `SecretReveal`, `TimePicker`, `MonthStepper`, `RuleBuilder`, `ResourcePropertyFilter`, `AuthParts` (17 ไฟล์เปลี่ยนมา import `Button` ตัวที่มี tooltip)
|
|
17
|
+
- `UiText.pagination` เพิ่ม `first` / `last` (หน้าแรก/หน้าสุดท้าย)
|
|
18
|
+
- showcase: หน้า `/components/button` เพิ่มแถวปุ่มไอคอน (auto / `tooltip="…"` / `tooltip={false}` / trigger ของ dialog / ห่อ `SimpleTooltip`)
|
|
19
|
+
|
|
5
20
|
## [0.26.1] — 2026-10-03
|
|
6
21
|
|
|
7
22
|
แก้ตามผล audit 2026-10-03 (`docs/reports/audit-2026-10-03.md`) — ไม่เปลี่ยน props
|
package/README.md
CHANGED
|
@@ -154,6 +154,33 @@ export default async function Layout({ children }) {
|
|
|
154
154
|
<div style={{ "--brand": tenant.color, "--brand-foreground": "oklch(0.985 0 0)" } as React.CSSProperties}>…</div>
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
### ปุ่มไอคอน — tooltip มาให้เอง (มาตรฐานทุกโครงการ)
|
|
158
|
+
|
|
159
|
+
ไอคอนที่ไม่มีข้อความกำกับ ต้องบอกได้ว่าคืออะไรเมื่อวางเมาส์ — `Button` ของแพ็กเกจ **ห่อ `Tooltip` ให้เองเมื่อมี `aria-label` และ children ไม่มีข้อความ** (ไอคอนหรือ `<span className="sr-only">` ไม่นับเป็นข้อความ) ทุก call site จึงได้ทันทีโดยไม่ต้องแก้อะไร รวมถึงปุ่มที่เป็น trigger ของ dialog/menu เพราะปุ่มอยู่ชั้นในสุดของ `render=`
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<Button variant="ghost" size="icon-sm" aria-label="ดู client secret"><KeySquareIcon /></Button>
|
|
163
|
+
// → hover/focus แล้วขึ้น "ดู client secret" โดยไม่ต้องห่อ SimpleTooltip
|
|
164
|
+
|
|
165
|
+
<FormDialog trigger={<Button size="icon-sm" aria-label="แก้ไข"><PencilIcon /></Button>} … />
|
|
166
|
+
// → ได้ tooltip ด้วย (ไม่ต้องมี prop ใหม่ที่ FormDialog/RowActions)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| ต้องการ | เขียน |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| ข้อความอื่นที่ไม่ใช่ `aria-label` (หรือปุ่มที่มีข้อความแล้วแต่อยากอธิบายเพิ่ม) | `tooltip="ตรวจสอบอีกครั้ง"` |
|
|
172
|
+
| ปิด (เช่นปุ่มในเมนูที่มีข้อความอยู่แล้ว) | `tooltip={false}` |
|
|
173
|
+
| เปลี่ยนด้าน / หน่วงเวลา | `tooltipSide="bottom"`, `tooltipDelay={0}` |
|
|
174
|
+
|
|
175
|
+
- **ไม่ต้องมี `TooltipProvider`** — Base UI ไม่บังคับ; ใส่ที่ root layout ได้ถ้าอยากคุม delay ร่วมกันทุก tooltip (ค่าเริ่มต้นของ Base UI = 600ms, `TooltipProvider` ของแพ็กเกจตั้ง 0)
|
|
176
|
+
- ปุ่มที่ถูกห่อ `SimpleTooltip` อยู่แล้ว **ไม่ขึ้นสองชั้น** (`SimpleTooltip` ปิดตัวอัตโนมัติให้ด้วย `ButtonTooltipSuppress`) — ถ้าแอปห่อ tooltip ด้วยกลไกอื่นเอง ครอบ `<ButtonTooltipSuppress>` หรือใส่ `tooltip={false}`
|
|
177
|
+
- จอสัมผัสไม่มี hover — `aria-label` ยังทำหน้าที่เดิมให้ screen reader; ข้อความที่ "ต้องอ่านได้ทุกคน" อย่าเก็บไว้ใน tooltip เท่านั้น
|
|
178
|
+
- ไม่มี `aria-label` แต่ใช้รูปแบบ shadcn `<span className="sr-only">ข้อความ</span>` ข้างไอคอน ก็ใช้ข้อความนั้นเป็น tooltip ให้เหมือนกัน
|
|
179
|
+
- ปุ่มที่ `disabled` ไม่ขึ้น tooltip (ปุ่มที่ปิดอยู่ไม่รับ pointer ตามสไตล์ของ shadcn) — ถ้าต้องอธิบายว่าทำไมกดไม่ได้ ให้ห่อ `SimpleTooltip` ที่ `<span>` ครอบปุ่มแทน
|
|
180
|
+
- มี `title="…"` อยู่เดิมให้เอาออก ไม่งั้นจะได้ tooltip ซ้อนกับของเบราว์เซอร์
|
|
181
|
+
- `<button>` ดิบ (ไม่ผ่าน `Button`) ไม่ได้ของนี้ — อีกเหตุผลที่ห้ามเขียนปุ่มเอง
|
|
182
|
+
- ปุ่มไอคอนของแพ็กเกจเอง (`RowActions`, `ActionMenu`, `SimpleDialog` ปุ่มปิด, `DataTable` แบ่งหน้า, `ThemeToggle`, `Notice`, `UploadList`, `TimePicker`, `MonthStepper`, `SecretReveal`, `CommandPalette` …) ได้ tooltip ไปพร้อมกันตั้งแต่ 0.27.0
|
|
183
|
+
|
|
157
184
|
### ยืนยันก่อนทำ — ห้ามใช้ `window.confirm`
|
|
158
185
|
|
|
159
186
|
webview/automation บางตัวปิด `window.confirm` ทิ้ง (กดแล้วไม่เกิดอะไร) และไม่เข้า design system — ใช้ 3 ตัวนี้แทน
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suphark/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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 เอง",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
4
5
|
import {
|
|
5
6
|
Select,
|
|
6
7
|
SelectContent,
|
|
@@ -23,6 +24,7 @@ export function DataTablePagination<TData>({
|
|
|
23
24
|
pageSizeOptions = [10, 20, 30, 40, 50],
|
|
24
25
|
onPageSizeChange,
|
|
25
26
|
}: DataTablePaginationProps<TData>) {
|
|
27
|
+
const text = useUiText();
|
|
26
28
|
const isRowSelectionEnabled = table.options.enableRowSelection !== false;
|
|
27
29
|
|
|
28
30
|
return (
|
|
@@ -71,40 +73,40 @@ export function DataTablePagination<TData>({
|
|
|
71
73
|
variant="outline"
|
|
72
74
|
size="icon"
|
|
73
75
|
className="hidden size-8 lg:flex"
|
|
76
|
+
aria-label={text.pagination?.first ?? "หน้าแรก"}
|
|
74
77
|
onClick={() => table.setPageIndex(0)}
|
|
75
78
|
disabled={!table.getCanPreviousPage()}
|
|
76
79
|
>
|
|
77
|
-
<span className="sr-only">Go to first page</span>
|
|
78
80
|
<ChevronsLeft />
|
|
79
81
|
</Button>
|
|
80
82
|
<Button
|
|
81
83
|
variant="outline"
|
|
82
84
|
size="icon"
|
|
83
85
|
className="size-8"
|
|
86
|
+
aria-label={text.pagination?.previous ?? "ก่อนหน้า"}
|
|
84
87
|
onClick={() => table.previousPage()}
|
|
85
88
|
disabled={!table.getCanPreviousPage()}
|
|
86
89
|
>
|
|
87
|
-
<span className="sr-only">Go to previous page</span>
|
|
88
90
|
<ChevronLeft />
|
|
89
91
|
</Button>
|
|
90
92
|
<Button
|
|
91
93
|
variant="outline"
|
|
92
94
|
size="icon"
|
|
93
95
|
className="size-8"
|
|
96
|
+
aria-label={text.pagination?.next ?? "ถัดไป"}
|
|
94
97
|
onClick={() => table.nextPage()}
|
|
95
98
|
disabled={!table.getCanNextPage()}
|
|
96
99
|
>
|
|
97
|
-
<span className="sr-only">Go to next page</span>
|
|
98
100
|
<ChevronRight />
|
|
99
101
|
</Button>
|
|
100
102
|
<Button
|
|
101
103
|
variant="outline"
|
|
102
104
|
size="icon"
|
|
103
105
|
className="hidden size-8 lg:flex"
|
|
106
|
+
aria-label={text.pagination?.last ?? "หน้าสุดท้าย"}
|
|
104
107
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
105
108
|
disabled={!table.getCanNextPage()}
|
|
106
109
|
>
|
|
107
|
-
<span className="sr-only">Go to last page</span>
|
|
108
110
|
<ChevronsRight />
|
|
109
111
|
</Button>
|
|
110
112
|
</div>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button as ButtonBase } from "@suphark/ui/components/ui/button";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@suphark/ui/components/ui/tooltip";
|
|
5
|
+
import {
|
|
6
|
+
Children,
|
|
7
|
+
type ComponentProps,
|
|
8
|
+
createContext,
|
|
9
|
+
Fragment,
|
|
10
|
+
isValidElement,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
useContext,
|
|
13
|
+
} from "react";
|
|
14
|
+
|
|
15
|
+
export type ButtonTooltipSide = "top" | "bottom" | "left" | "right";
|
|
16
|
+
|
|
17
|
+
export interface ButtonProps extends ComponentProps<typeof ButtonBase> {
|
|
18
|
+
/**
|
|
19
|
+
* ข้อความ tooltip — ไม่ใส่ = ใช้ `aria-label` (หรือ `<span className="sr-only">` แบบ shadcn)
|
|
20
|
+
* ให้เองเมื่อปุ่ม "มีแต่ไอคอน" (ไม่มี children ที่เป็นข้อความ)
|
|
21
|
+
* ใส่ข้อความ = ทับค่าอัตโนมัติ (ใช้กับปุ่มที่มีข้อความแล้วแต่ยังอยากอธิบายเพิ่ม) · `false` = ปิด
|
|
22
|
+
*/
|
|
23
|
+
tooltip?: ReactNode | false;
|
|
24
|
+
tooltipSide?: ButtonTooltipSide;
|
|
25
|
+
/** หน่วงก่อนโชว์ (ms) — ไม่ใส่ = ตาม `TooltipProvider` ของแอป หรือ 600 ตามค่าของ Base UI */
|
|
26
|
+
tooltipDelay?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NoAutoTooltip = createContext(false);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ปิด tooltip อัตโนมัติของ `Button` ทั้งซับทรี — `SimpleTooltip` ใส่ให้แล้ว
|
|
33
|
+
* (ปุ่มที่ถูกห่อ tooltip อยู่แล้วจะไม่ขึ้นสองชั้น) ใช้เองได้เมื่อแอปห่อ tooltip ด้วยกลไกอื่น
|
|
34
|
+
*/
|
|
35
|
+
export function ButtonTooltipSuppress({ children }: { children: ReactNode }) {
|
|
36
|
+
return <NoAutoTooltip.Provider value={true}>{children}</NoAutoTooltip.Provider>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** ข้อความใน `<span className="sr-only">` (รูปแบบของ shadcn ที่ใช้แทน aria-label) */
|
|
40
|
+
function srOnlyText(children: ReactNode): string | null {
|
|
41
|
+
let found: string | null = null;
|
|
42
|
+
Children.forEach(children, (child) => {
|
|
43
|
+
if (found || !isValidElement(child)) return;
|
|
44
|
+
const props = child.props as { className?: unknown; children?: ReactNode };
|
|
45
|
+
if (child.type === Fragment) {
|
|
46
|
+
found = srOnlyText(props.children);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (typeof props.className !== "string" || !props.className.split(/\s+/).includes("sr-only"))
|
|
50
|
+
return;
|
|
51
|
+
if (typeof props.children === "string" && props.children.trim() !== "")
|
|
52
|
+
found = props.children.trim();
|
|
53
|
+
});
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** มี children ที่เป็นข้อความจริง ๆ ไหม (ไอคอน/`<span className="sr-only">` ไม่นับ) */
|
|
58
|
+
function hasTextChild(children: ReactNode): boolean {
|
|
59
|
+
let found = false;
|
|
60
|
+
Children.forEach(children, (child) => {
|
|
61
|
+
if (found) return;
|
|
62
|
+
if (typeof child === "string") {
|
|
63
|
+
if (child.trim() !== "") found = true;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (typeof child === "number") {
|
|
67
|
+
found = true;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// <>{icon} ข้อความ</> — Children.forEach ไม่ไล่เข้า fragment ให้
|
|
71
|
+
if (isValidElement(child) && child.type === Fragment) {
|
|
72
|
+
const inner = (child.props as { children?: ReactNode }).children;
|
|
73
|
+
if (hasTextChild(inner)) found = true;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* ปุ่มของแพ็กเกจ = `Button` ของ shadcn + **tooltip อัตโนมัติสำหรับปุ่มไอคอน**
|
|
81
|
+
* มาตรฐานของทุกโครงการ: ไอคอนที่ไม่มีข้อความกำกับ ต้องบอกได้ว่าคืออะไรเมื่อวางเมาส์
|
|
82
|
+
* → ถ้ามี `aria-label` และ children ไม่มีข้อความ จะห่อ `Tooltip` ให้เอง (ทำที่นี่ที่เดียว
|
|
83
|
+
* call site ทุกที่จึงได้ทันที รวมถึงปุ่มที่เป็น trigger ของ dialog/menu เพราะปุ่มอยู่ชั้นในสุด)
|
|
84
|
+
* ปิดเป็นรายปุ่มด้วย `tooltip={false}`; ทับข้อความด้วย `tooltip="…"`
|
|
85
|
+
* ไฟล์ shadcn ดิบอยู่ที่ `components/ui/button.tsx` (ห้ามแก้มือ) — barrel export ตัวนี้
|
|
86
|
+
*/
|
|
87
|
+
export function Button({ tooltip, tooltipSide = "top", tooltipDelay, ...props }: ButtonProps) {
|
|
88
|
+
const suppressed = useContext(NoAutoTooltip);
|
|
89
|
+
const label =
|
|
90
|
+
typeof props["aria-label"] === "string" && props["aria-label"].trim() !== ""
|
|
91
|
+
? props["aria-label"]
|
|
92
|
+
: srOnlyText(props.children);
|
|
93
|
+
const explicit = tooltip !== undefined && tooltip !== null && tooltip !== "";
|
|
94
|
+
const auto =
|
|
95
|
+
!suppressed &&
|
|
96
|
+
tooltip === undefined &&
|
|
97
|
+
typeof label === "string" &&
|
|
98
|
+
!hasTextChild(props.children);
|
|
99
|
+
const content = tooltip === false ? null : explicit ? tooltip : auto ? label : null;
|
|
100
|
+
|
|
101
|
+
if (content === null || content === undefined) return <ButtonBase {...props} />;
|
|
102
|
+
return (
|
|
103
|
+
<Tooltip>
|
|
104
|
+
<TooltipTrigger delay={tooltipDelay} render={<ButtonBase {...props} />} />
|
|
105
|
+
<TooltipContent side={tooltipSide} className="max-w-xs text-pretty">
|
|
106
|
+
{content}
|
|
107
|
+
</TooltipContent>
|
|
108
|
+
</Tooltip>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { AddNewButton } from "./add-new-button";
|
|
6
6
|
export { ActionButton, type ActionButtonProps } from "./base/action-button";
|
|
7
|
+
export {
|
|
8
|
+
Button,
|
|
9
|
+
type ButtonProps,
|
|
10
|
+
type ButtonTooltipSide,
|
|
11
|
+
ButtonTooltipSuppress,
|
|
12
|
+
} from "./button";
|
|
7
13
|
export { CancelButton } from "./cancel-button";
|
|
8
14
|
export { ConfirmButton } from "./confirm-button";
|
|
9
15
|
export { CopyButton, type CopyButtonProps } from "./copy-button";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
3
4
|
import { Alert, AlertAction, AlertDescription, AlertTitle } from "@suphark/ui/components/ui/alert";
|
|
4
|
-
import { Button } from "@suphark/ui/components/ui/button";
|
|
5
5
|
import { cn } from "@suphark/ui/lib/utils";
|
|
6
6
|
import { CircleAlertIcon, CircleCheckIcon, InfoIcon, TriangleAlertIcon, XIcon } from "lucide-react";
|
|
7
7
|
import { type ComponentProps, type ReactNode, useState } from "react";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
3
4
|
import { Badge } from "@suphark/ui/components/ui/badge";
|
|
4
|
-
import { Button } from "@suphark/ui/components/ui/button";
|
|
5
5
|
import { Checkbox } from "@suphark/ui/components/ui/checkbox";
|
|
6
6
|
import { Empty, EmptyHeader, EmptyTitle } from "@suphark/ui/components/ui/empty";
|
|
7
7
|
import { Field, FieldGroup, FieldLabel } from "@suphark/ui/components/ui/field";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { copyText } from "@suphark/ui/lib/copy-text";
|
|
5
5
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
6
6
|
import { useState } from "react";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { cn } from "@suphark/ui/lib/utils";
|
|
5
5
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
6
|
import { useUiText } from "./ui-text";
|
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@suphark/ui/components/ui/tooltip";
|
|
12
12
|
import { cn } from "@suphark/ui/lib/utils";
|
|
13
13
|
import type { ReactElement, ReactNode } from "react";
|
|
14
|
+
import { ButtonTooltipSuppress } from "./button/button";
|
|
14
15
|
|
|
15
16
|
type Side = "top" | "bottom" | "left" | "right";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* tooltip สำเร็จรูป: ห่อ element เดียว + ข้อความ (แทน Tooltip/TooltipTrigger/TooltipContent 3 ชิ้น)
|
|
19
20
|
* `content` ว่าง = ไม่มี tooltip (ไม่ต้องเขียนเงื่อนไขเอง) · ปุ่มไอคอนอย่างเดียวยังต้องมี aria-label ของตัวเอง
|
|
21
|
+
* ปุ่มไอคอน (`Button` + `aria-label`) **ไม่ต้องห่อตัวนี้แล้ว** ตั้งแต่ 0.27.0 — ห่อได้ถ้าอยากใช้ข้อความอื่น
|
|
20
22
|
* คำอธิบายยาวหลังไอคอน (i) ใช้ `InfoHint`
|
|
21
23
|
*
|
|
22
24
|
* <SimpleTooltip content="คัดลอกลิงก์"><Button size="icon" aria-label="คัดลอกลิงก์"><Copy /></Button></SimpleTooltip>
|
|
@@ -34,13 +36,16 @@ export function SimpleTooltip({
|
|
|
34
36
|
className?: string;
|
|
35
37
|
}) {
|
|
36
38
|
if (content === null || content === undefined || content === "") return children;
|
|
39
|
+
// ลูกที่เป็น `Button` ของแพ็กเกจจะไม่สร้าง tooltip อัตโนมัติซ้อนขึ้นมาอีกชั้น
|
|
37
40
|
return (
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
<ButtonTooltipSuppress>
|
|
42
|
+
<Tooltip>
|
|
43
|
+
<TooltipTrigger render={children} />
|
|
44
|
+
<TooltipContent side={side} className={cn("max-w-xs text-pretty", className)}>
|
|
45
|
+
{content}
|
|
46
|
+
</TooltipContent>
|
|
47
|
+
</Tooltip>
|
|
48
|
+
</ButtonTooltipSuppress>
|
|
44
49
|
);
|
|
45
50
|
}
|
|
46
51
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { MoonIcon, SunIcon } from "lucide-react";
|
|
5
5
|
import { useTheme } from "next-themes";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
4
5
|
import {
|
|
5
6
|
DropdownMenu,
|
|
6
7
|
DropdownMenuContent,
|
|
@@ -30,6 +31,8 @@ export function ThemeToggle({ persistedTheme, onThemeChange }: ThemeToggleProps)
|
|
|
30
31
|
lastPersisted.current = persistedTheme;
|
|
31
32
|
}, [persistedTheme]);
|
|
32
33
|
|
|
34
|
+
const text = useUiText();
|
|
35
|
+
|
|
33
36
|
const handleThemeChange = async (newTheme: ThemeValue) => {
|
|
34
37
|
setTheme(newTheme);
|
|
35
38
|
await onThemeChange?.(newTheme);
|
|
@@ -43,13 +46,12 @@ export function ThemeToggle({ persistedTheme, onThemeChange }: ThemeToggleProps)
|
|
|
43
46
|
variant="ghost"
|
|
44
47
|
size="icon"
|
|
45
48
|
className="relative h-9 w-9 rounded-md hover:bg-accent hover:text-accent-foreground"
|
|
46
|
-
|
|
49
|
+
aria-label={text.theme?.label ?? "Change theme"}
|
|
47
50
|
/>
|
|
48
51
|
}
|
|
49
52
|
>
|
|
50
53
|
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
51
54
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
52
|
-
<span className="sr-only">Change theme</span>
|
|
53
55
|
</DropdownMenuTrigger>
|
|
54
56
|
<DropdownMenuContent align="end" className="w-36">
|
|
55
57
|
<DropdownMenuItem
|
|
@@ -81,8 +81,15 @@ export interface UiText {
|
|
|
81
81
|
}>;
|
|
82
82
|
/** ChatThread (optional) */
|
|
83
83
|
chat?: Partial<{ placeholder: string; send: string; typing: string; scrollToLatest: string }>;
|
|
84
|
-
/** SimplePagination (optional) */
|
|
85
|
-
pagination?: Partial<{
|
|
84
|
+
/** SimplePagination / DataTablePagination (optional) */
|
|
85
|
+
pagination?: Partial<{
|
|
86
|
+
previous: string;
|
|
87
|
+
next: string;
|
|
88
|
+
first: string;
|
|
89
|
+
last: string;
|
|
90
|
+
label: string;
|
|
91
|
+
page: string;
|
|
92
|
+
}>;
|
|
86
93
|
/** TableSkeleton/ListSkeleton/CardSkeleton (optional) — ข้อความสำหรับ screen reader */
|
|
87
94
|
loading?: string;
|
|
88
95
|
/** สถานะออนไลน์ของ UserIdentity/Initials (optional) */
|
|
@@ -174,7 +181,14 @@ export const UI_TEXT_TH: UiText = {
|
|
|
174
181
|
typing: "กำลังพิมพ์…",
|
|
175
182
|
scrollToLatest: "ไปข้อความล่าสุด",
|
|
176
183
|
},
|
|
177
|
-
pagination: {
|
|
184
|
+
pagination: {
|
|
185
|
+
previous: "ก่อนหน้า",
|
|
186
|
+
next: "ถัดไป",
|
|
187
|
+
first: "หน้าแรก",
|
|
188
|
+
last: "หน้าสุดท้าย",
|
|
189
|
+
label: "แบ่งหน้า",
|
|
190
|
+
page: "หน้า",
|
|
191
|
+
},
|
|
178
192
|
loading: "กำลังโหลด…",
|
|
179
193
|
presence: { online: "ออนไลน์", away: "ไม่อยู่", busy: "ไม่ว่าง", offline: "ออฟไลน์" },
|
|
180
194
|
characterCount: "พิมพ์แล้ว {count} จาก {max} ตัวอักษร",
|
|
@@ -264,7 +278,14 @@ export const UI_TEXT_EN: UiText = {
|
|
|
264
278
|
typing: "Typing…",
|
|
265
279
|
scrollToLatest: "Jump to latest",
|
|
266
280
|
},
|
|
267
|
-
pagination: {
|
|
281
|
+
pagination: {
|
|
282
|
+
previous: "Previous",
|
|
283
|
+
next: "Next",
|
|
284
|
+
first: "First page",
|
|
285
|
+
last: "Last page",
|
|
286
|
+
label: "Pagination",
|
|
287
|
+
page: "Page",
|
|
288
|
+
},
|
|
268
289
|
loading: "Loading…",
|
|
269
290
|
presence: { online: "Online", away: "Away", busy: "Busy", offline: "Offline" },
|
|
270
291
|
characterCount: "{count} of {max} characters",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Button } from "@suphark/ui/components/
|
|
3
|
+
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { formatBytes } from "@suphark/ui/lib/formatters";
|
|
5
5
|
import { cn } from "@suphark/ui/lib/utils";
|
|
6
6
|
import { AlertCircle, Check, ChevronDown, RotateCcw, X } from "lucide-react";
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,12 @@ export {
|
|
|
40
40
|
SaveButton,
|
|
41
41
|
SubmitButton,
|
|
42
42
|
} from "./components/shared/button";
|
|
43
|
+
export {
|
|
44
|
+
Button,
|
|
45
|
+
type ButtonProps,
|
|
46
|
+
type ButtonTooltipSide,
|
|
47
|
+
ButtonTooltipSuppress,
|
|
48
|
+
} from "./components/shared/button/button";
|
|
43
49
|
export {
|
|
44
50
|
type CalendarDayContext,
|
|
45
51
|
type CalendarDayState,
|
|
@@ -296,7 +302,7 @@ export {
|
|
|
296
302
|
BreadcrumbSeparator,
|
|
297
303
|
} from "./components/ui/breadcrumb";
|
|
298
304
|
export { Bubble, BubbleContent, BubbleGroup, BubbleReactions } from "./components/ui/bubble";
|
|
299
|
-
export {
|
|
305
|
+
export { buttonVariants } from "./components/ui/button";
|
|
300
306
|
export {
|
|
301
307
|
ButtonGroup,
|
|
302
308
|
ButtonGroupSeparator,
|