@suphark/ui 0.28.0 → 0.30.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 +31 -0
- package/README.md +26 -0
- package/package.json +2 -1
- package/scripts/hardcoded-text.d.mts +3 -0
- package/scripts/hardcoded-text.mjs +101 -0
- package/src/components/app-shell/app-shell.tsx +27 -8
- package/src/components/app-shell/sub-nav.tsx +8 -4
- package/src/components/app-shell/top-tabs.tsx +6 -1
- package/src/components/data-table/data-table-faceted-filter.tsx +3 -1
- package/src/components/data-table/data-table-toolbar.tsx +13 -6
- package/src/components/shared/form/form-field-combobox.tsx +9 -2
- package/src/components/shared/inline-edit.tsx +37 -3
- package/src/components/shared/layout/error-state.tsx +8 -4
- package/src/components/shared/month-stepper.tsx +8 -6
- package/src/components/shared/multi-select-combobox.tsx +11 -4
- package/src/components/shared/resource-property-filter.tsx +33 -16
- package/src/components/shared/segmented-control.tsx +17 -1
- package/src/components/shared/ui-text.tsx +147 -0
- package/src/components/sortable/sortable-list.tsx +450 -0
- package/src/index.ts +5 -1
- package/src/lib/sortable-order.ts +142 -0
- package/src/sortable.ts +31 -0
|
@@ -62,6 +62,8 @@ export function MonthStepper({
|
|
|
62
62
|
const nowInRange = (!min || compareMonth(now, min) >= 0) && (!max || compareMonth(now, max) <= 0);
|
|
63
63
|
const label = formatLabel ? formatLabel(value) : formatMonthKey(value, locale, format);
|
|
64
64
|
const isCurrent = value === now;
|
|
65
|
+
// กดชื่อเดือนเพื่อกลับเดือนนี้ได้ก็ต่อเมื่อยังไม่ได้อยู่เดือนนี้ และเดือนนี้อยู่ในช่วงที่เลือกได้
|
|
66
|
+
const canReset = resetToCurrent && !isCurrent && nowInRange;
|
|
65
67
|
|
|
66
68
|
return (
|
|
67
69
|
<div
|
|
@@ -78,23 +80,23 @@ export function MonthStepper({
|
|
|
78
80
|
>
|
|
79
81
|
<ChevronLeft />
|
|
80
82
|
</Button>
|
|
81
|
-
{
|
|
83
|
+
{canReset ? (
|
|
82
84
|
<Button
|
|
83
85
|
type="button"
|
|
84
86
|
variant="ghost"
|
|
85
|
-
|
|
86
|
-
// ปุ่มกลางแสดงชื่อเดือนเสมอ — ปิดเมื่ออยู่เดือนนี้แล้วแต่ยังต้องอ่านออก ไม่ให้จางแบบ disabled ปกติ
|
|
87
|
-
className="min-w-32 gap-2 tabular-nums disabled:opacity-100"
|
|
87
|
+
className="min-w-32 gap-2 tabular-nums"
|
|
88
88
|
onClick={() => onValueChange(now)}
|
|
89
|
-
|
|
89
|
+
tooltip={text?.current ?? "กลับไปเดือนนี้"}
|
|
90
90
|
aria-live="polite"
|
|
91
91
|
>
|
|
92
92
|
<CalendarDays className="text-muted-foreground" />
|
|
93
93
|
{label}
|
|
94
94
|
</Button>
|
|
95
95
|
) : (
|
|
96
|
+
// อยู่เดือนนี้แล้ว (หรือปิด resetToCurrent) = ไม่มีอะไรให้กด — เป็นป้ายชื่อเดือน ไม่ใช่ปุ่มที่ปิดอยู่
|
|
97
|
+
// (เดิมเป็น <Button disabled> ซึ่ง screen reader อ่านว่า "ปุ่ม, ปิดใช้งาน" ทั้งที่มันคือป้าย — DueBell รายงาน)
|
|
96
98
|
<span
|
|
97
|
-
className="flex min-w-32 items-center justify-center gap-2 px-2 font-medium text-sm tabular-nums"
|
|
99
|
+
className="flex h-8 min-w-32 items-center justify-center gap-2 px-2 font-medium text-sm tabular-nums"
|
|
98
100
|
aria-live="polite"
|
|
99
101
|
>
|
|
100
102
|
<CalendarDays className="size-4 text-muted-foreground" />
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
4
|
import { SwitcherChevron } from "@suphark/ui/components/shared/switcher-chevron";
|
|
5
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
5
6
|
import { Badge } from "@suphark/ui/components/ui/badge";
|
|
6
7
|
import {
|
|
7
8
|
Command,
|
|
@@ -90,6 +91,7 @@ export function MultiSelectCombobox({
|
|
|
90
91
|
ariaLabel,
|
|
91
92
|
wrapText = false,
|
|
92
93
|
}: MultiSelectComboboxProps) {
|
|
94
|
+
const text = useUiText();
|
|
93
95
|
const [open, setOpen] = React.useState(false);
|
|
94
96
|
const [inputValue, setInputValue] = React.useState("");
|
|
95
97
|
|
|
@@ -213,7 +215,10 @@ export function MultiSelectCombobox({
|
|
|
213
215
|
<span
|
|
214
216
|
role="button"
|
|
215
217
|
tabIndex={0}
|
|
216
|
-
aria-label={
|
|
218
|
+
aria-label={(text.combobox?.remove ?? "ลบ {label}").replace(
|
|
219
|
+
"{label}",
|
|
220
|
+
getLabel(val),
|
|
221
|
+
)}
|
|
217
222
|
onKeyDown={(e) => {
|
|
218
223
|
if (e.key === "Enter" || e.key === " ") {
|
|
219
224
|
e.preventDefault();
|
|
@@ -246,8 +251,8 @@ export function MultiSelectCombobox({
|
|
|
246
251
|
<span
|
|
247
252
|
role="button"
|
|
248
253
|
tabIndex={0}
|
|
249
|
-
title="ล้างทั้งหมด"
|
|
250
|
-
aria-label="ล้างทั้งหมด"
|
|
254
|
+
title={text.combobox?.clearAll ?? "ล้างทั้งหมด"}
|
|
255
|
+
aria-label={text.combobox?.clearAll ?? "ล้างทั้งหมด"}
|
|
251
256
|
onClick={handleClearAll}
|
|
252
257
|
onKeyDown={(e) => {
|
|
253
258
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -378,7 +383,9 @@ export function MultiSelectCombobox({
|
|
|
378
383
|
className="flex cursor-pointer items-center gap-2 text-primary"
|
|
379
384
|
>
|
|
380
385
|
<Plus className="size-4" />
|
|
381
|
-
<span
|
|
386
|
+
<span>
|
|
387
|
+
{(text.combobox?.add ?? "เพิ่ม “{value}”").replace("{value}", inputValue)}
|
|
388
|
+
</span>
|
|
382
389
|
</CommandItem>
|
|
383
390
|
</CommandGroup>
|
|
384
391
|
</>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Button } from "@suphark/ui/components/shared/button/button";
|
|
4
|
+
import { useUiText } from "@suphark/ui/components/shared/ui-text";
|
|
4
5
|
import { Badge } from "@suphark/ui/components/ui/badge";
|
|
5
6
|
import { Checkbox } from "@suphark/ui/components/ui/checkbox";
|
|
6
7
|
import { Empty, EmptyHeader, EmptyTitle } from "@suphark/ui/components/ui/empty";
|
|
@@ -94,6 +95,7 @@ export function ResourcePropertyFilter({
|
|
|
94
95
|
triggerLabel = "คุณสมบัติ Resource",
|
|
95
96
|
triggerClassName,
|
|
96
97
|
}: ResourcePropertyFilterProps) {
|
|
98
|
+
const text = useUiText();
|
|
97
99
|
const idPrefix = useId();
|
|
98
100
|
const [propertySearch, setPropertySearch] = useState("");
|
|
99
101
|
const deferredPropertySearch = useDeferredValue(propertySearch);
|
|
@@ -136,8 +138,10 @@ export function ResourcePropertyFilter({
|
|
|
136
138
|
<PopoverHeader className="border-b p-4">
|
|
137
139
|
<div className="flex items-start justify-between gap-3">
|
|
138
140
|
<div className="flex flex-col gap-1">
|
|
139
|
-
<PopoverTitle
|
|
140
|
-
<PopoverDescription
|
|
141
|
+
<PopoverTitle>{text.filter?.title ?? "กรองตามคุณสมบัติ"}</PopoverTitle>
|
|
142
|
+
<PopoverDescription>
|
|
143
|
+
{text.filter?.description ?? "ค่าในคุณสมบัติเดียวกันเป็น OR และคนละคุณสมบัติเป็น AND"}
|
|
144
|
+
</PopoverDescription>
|
|
141
145
|
</div>
|
|
142
146
|
{activeCount > 0 ? (
|
|
143
147
|
<Button type="button" variant="ghost" size="xs" onClick={() => onChange({})}>
|
|
@@ -155,15 +159,15 @@ export function ResourcePropertyFilter({
|
|
|
155
159
|
<InputGroupInput
|
|
156
160
|
value={propertySearch}
|
|
157
161
|
onChange={(event) => setPropertySearch(event.target.value)}
|
|
158
|
-
placeholder=
|
|
159
|
-
aria-label="
|
|
162
|
+
placeholder={text.filter?.searchProperty ?? "ค้นหาคุณสมบัติ…"}
|
|
163
|
+
aria-label={text.filter?.searchProperty ?? "ค้นหาคุณสมบัติ…"}
|
|
160
164
|
/>
|
|
161
165
|
{propertySearch ? (
|
|
162
166
|
<InputGroupAddon align="inline-end">
|
|
163
167
|
<InputGroupButton
|
|
164
168
|
size="icon-xs"
|
|
165
169
|
onClick={() => setPropertySearch("")}
|
|
166
|
-
aria-label="ล้างคำค้นหา"
|
|
170
|
+
aria-label={text.filter?.clearSearch ?? "ล้างคำค้นหา"}
|
|
167
171
|
>
|
|
168
172
|
<X />
|
|
169
173
|
</InputGroupButton>
|
|
@@ -197,7 +201,10 @@ export function ResourcePropertyFilter({
|
|
|
197
201
|
variant="ghost"
|
|
198
202
|
size="icon-xs"
|
|
199
203
|
onClick={() => clearFilter(definition.propertyTemplateId)}
|
|
200
|
-
aria-label={
|
|
204
|
+
aria-label={(text.filter?.clearOne ?? "ล้างตัวกรอง {name}").replace(
|
|
205
|
+
"{name}",
|
|
206
|
+
definition.propertyName,
|
|
207
|
+
)}
|
|
201
208
|
>
|
|
202
209
|
<X />
|
|
203
210
|
</Button>
|
|
@@ -230,8 +237,11 @@ export function ResourcePropertyFilter({
|
|
|
230
237
|
query: event.target.value,
|
|
231
238
|
})
|
|
232
239
|
}
|
|
233
|
-
placeholder="
|
|
234
|
-
aria-label={
|
|
240
|
+
placeholder={text.filter?.searchValue ?? "ค้นหาข้อความ…"}
|
|
241
|
+
aria-label={(text.filter?.filterOne ?? "กรอง {name}").replace(
|
|
242
|
+
"{name}",
|
|
243
|
+
definition.propertyName,
|
|
244
|
+
)}
|
|
235
245
|
/>
|
|
236
246
|
)}
|
|
237
247
|
</Field>
|
|
@@ -241,7 +251,7 @@ export function ResourcePropertyFilter({
|
|
|
241
251
|
) : (
|
|
242
252
|
<Empty className="min-h-40 rounded-none border-0">
|
|
243
253
|
<EmptyHeader>
|
|
244
|
-
<EmptyTitle
|
|
254
|
+
<EmptyTitle>{text.filter?.noProperties ?? "ไม่พบคุณสมบัติ"}</EmptyTitle>
|
|
245
255
|
</EmptyHeader>
|
|
246
256
|
</Empty>
|
|
247
257
|
)}
|
|
@@ -250,7 +260,10 @@ export function ResourcePropertyFilter({
|
|
|
250
260
|
</Popover>
|
|
251
261
|
|
|
252
262
|
{activeCount > 0 ? (
|
|
253
|
-
<fieldset
|
|
263
|
+
<fieldset
|
|
264
|
+
className="flex flex-wrap gap-1 border-0 p-0"
|
|
265
|
+
aria-label={text.filter?.active ?? "ตัวกรองคุณสมบัติที่ใช้งาน"}
|
|
266
|
+
>
|
|
254
267
|
{definitions.map((definition) => {
|
|
255
268
|
const filter = filters[definition.propertyTemplateId];
|
|
256
269
|
if (!isResourcePropertyFilterActive(filter)) return null;
|
|
@@ -265,7 +278,10 @@ export function ResourcePropertyFilter({
|
|
|
265
278
|
variant="ghost"
|
|
266
279
|
size="icon-xs"
|
|
267
280
|
onClick={() => clearFilter(definition.propertyTemplateId)}
|
|
268
|
-
aria-label={
|
|
281
|
+
aria-label={(text.filter?.clearOne ?? "ล้างตัวกรอง {name}").replace(
|
|
282
|
+
"{name}",
|
|
283
|
+
definition.propertyName,
|
|
284
|
+
)}
|
|
269
285
|
>
|
|
270
286
|
<X />
|
|
271
287
|
</Button>
|
|
@@ -327,6 +343,7 @@ function NumberPropertyField({
|
|
|
327
343
|
filter?: Extract<ResourcePropertyFilterValue, { dataType: "NUMBER" }>;
|
|
328
344
|
onChange: (filter: Extract<ResourcePropertyFilterValue, { dataType: "NUMBER" }>) => void;
|
|
329
345
|
}) {
|
|
346
|
+
const text = useUiText();
|
|
330
347
|
const currentFilter = filter ?? {
|
|
331
348
|
dataType: "NUMBER" as const,
|
|
332
349
|
min: "",
|
|
@@ -342,8 +359,8 @@ function NumberPropertyField({
|
|
|
342
359
|
step="any"
|
|
343
360
|
value={currentFilter.min}
|
|
344
361
|
onChange={(event) => onChange({ ...currentFilter, min: event.target.value })}
|
|
345
|
-
placeholder="ต่ำสุด"
|
|
346
|
-
aria-label={`${definition.propertyName}
|
|
362
|
+
placeholder={text.filter?.min ?? "ต่ำสุด"}
|
|
363
|
+
aria-label={`${definition.propertyName} ${text.filter?.min ?? "ต่ำสุด"}`}
|
|
347
364
|
/>
|
|
348
365
|
<Input
|
|
349
366
|
type="number"
|
|
@@ -351,8 +368,8 @@ function NumberPropertyField({
|
|
|
351
368
|
step="any"
|
|
352
369
|
value={currentFilter.max}
|
|
353
370
|
onChange={(event) => onChange({ ...currentFilter, max: event.target.value })}
|
|
354
|
-
placeholder="สูงสุด"
|
|
355
|
-
aria-label={`${definition.propertyName}
|
|
371
|
+
placeholder={text.filter?.max ?? "สูงสุด"}
|
|
372
|
+
aria-label={`${definition.propertyName} ${text.filter?.max ?? "สูงสุด"}`}
|
|
356
373
|
/>
|
|
357
374
|
{definition.units.length > 0 ? (
|
|
358
375
|
<Select
|
|
@@ -360,7 +377,7 @@ function NumberPropertyField({
|
|
|
360
377
|
onValueChange={(unitId) => onChange({ ...currentFilter, unitId })}
|
|
361
378
|
>
|
|
362
379
|
<SelectTrigger className="col-span-2 w-full">
|
|
363
|
-
<SelectValue placeholder="เลือกหน่วย" />
|
|
380
|
+
<SelectValue placeholder={text.filter?.unit ?? "เลือกหน่วย"} />
|
|
364
381
|
</SelectTrigger>
|
|
365
382
|
<SelectContent>
|
|
366
383
|
<SelectGroup>
|
|
@@ -5,12 +5,28 @@ import { cn } from "@suphark/ui/lib/utils";
|
|
|
5
5
|
import type { ReactNode } from "react";
|
|
6
6
|
import { CountBadge } from "./count-badge";
|
|
7
7
|
|
|
8
|
+
export type SegmentedTone = "default" | "success" | "warning" | "destructive" | "info";
|
|
9
|
+
|
|
10
|
+
/** สีตอนถูกเลือกเท่านั้น — ตัวที่ไม่ได้เลือกคงหน้าตากลาง ๆ ไม่ให้แถบกลายเป็นรุ้ง */
|
|
11
|
+
const TONE: Record<SegmentedTone, string> = {
|
|
12
|
+
default: "",
|
|
13
|
+
success: "aria-pressed:bg-success-subtle aria-pressed:text-success-subtle-foreground",
|
|
14
|
+
warning: "aria-pressed:bg-warning-subtle aria-pressed:text-warning-subtle-foreground",
|
|
15
|
+
destructive: "aria-pressed:bg-destructive-subtle aria-pressed:text-destructive-subtle-foreground",
|
|
16
|
+
info: "aria-pressed:bg-info-subtle aria-pressed:text-info-subtle-foreground",
|
|
17
|
+
};
|
|
18
|
+
|
|
8
19
|
export interface SegmentedOption<T extends string> {
|
|
9
20
|
value: T;
|
|
10
21
|
label: ReactNode;
|
|
11
22
|
icon?: ReactNode;
|
|
12
23
|
/** จำนวนท้ายตัวเลือก เช่นจำนวนรายการในตัวกรองนั้น */
|
|
13
24
|
count?: number;
|
|
25
|
+
/**
|
|
26
|
+
* สีตามความหมายของตัวเลือกนั้นเมื่อถูกเลือก (รายรับ = success, รายจ่าย = destructive)
|
|
27
|
+
* ไม่ส่ง = พื้นเทาเหมือนเดิม — ใช้เมื่อ "ประเภท" มีความหมายทางสี ไม่ใช่ตกแต่ง
|
|
28
|
+
*/
|
|
29
|
+
tone?: SegmentedTone;
|
|
14
30
|
disabled?: boolean;
|
|
15
31
|
}
|
|
16
32
|
|
|
@@ -59,7 +75,7 @@ export function SegmentedControl<T extends string>({
|
|
|
59
75
|
key={o.value}
|
|
60
76
|
value={o.value}
|
|
61
77
|
disabled={o.disabled}
|
|
62
|
-
className={cn("gap-1.5", fullWidth && "flex-1")}
|
|
78
|
+
className={cn("gap-1.5", TONE[o.tone ?? "default"], fullWidth && "flex-1")}
|
|
63
79
|
>
|
|
64
80
|
{o.icon}
|
|
65
81
|
{o.label}
|
|
@@ -22,6 +22,49 @@ export interface UiText {
|
|
|
22
22
|
actionFailed?: string;
|
|
23
23
|
/** InlineEdit: ปุ่มดินสอ (optional — "แก้ไข") */
|
|
24
24
|
edit?: string;
|
|
25
|
+
/** SortableList (optional) — ที่จับลาก + คำอธิบายสำหรับ screen reader */
|
|
26
|
+
sortable?: Partial<{
|
|
27
|
+
handle: string;
|
|
28
|
+
instructions: string;
|
|
29
|
+
/** ใช้ `{index}` / `{total}` */
|
|
30
|
+
started: string;
|
|
31
|
+
moved: string;
|
|
32
|
+
dropped: string;
|
|
33
|
+
cancelled: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** MultiSelectCombobox / FormFieldCombobox (optional) — `search`/`remove` ใช้ `{label}`, `add` ใช้ `{value}` */
|
|
36
|
+
combobox?: Partial<{
|
|
37
|
+
search: string;
|
|
38
|
+
empty: string;
|
|
39
|
+
clearAll: string;
|
|
40
|
+
remove: string;
|
|
41
|
+
add: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** ResourcePropertyFilter (optional) — ตัวที่ใช้ `{name}` คือชื่อคุณสมบัติ */
|
|
44
|
+
filter?: Partial<{
|
|
45
|
+
title: string;
|
|
46
|
+
description: string;
|
|
47
|
+
searchProperty: string;
|
|
48
|
+
clearSearch: string;
|
|
49
|
+
noProperties: string;
|
|
50
|
+
active: string;
|
|
51
|
+
searchValue: string;
|
|
52
|
+
filterOne: string;
|
|
53
|
+
clearOne: string;
|
|
54
|
+
min: string;
|
|
55
|
+
max: string;
|
|
56
|
+
unit: string;
|
|
57
|
+
}>;
|
|
58
|
+
/** AppShell เมนู (optional) — `noResults` ใช้ `{query}` */
|
|
59
|
+
nav?: Partial<{
|
|
60
|
+
main: string;
|
|
61
|
+
sub: string;
|
|
62
|
+
choosePage: string;
|
|
63
|
+
noResults: string;
|
|
64
|
+
clearSearch: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** ErrorState เซสชันหมดอายุ (optional) */
|
|
67
|
+
sessionExpired?: Partial<{ title: string; description: string; signIn: string }>;
|
|
25
68
|
/** SelectionBar (optional) — `selected` ใช้ `{count}` */
|
|
26
69
|
selection?: Partial<{ selected: string; clear: string }>;
|
|
27
70
|
/** DataTable (optional) — `rowsSelected` ใช้ `{selected}`/`{total}`, `pageOf` ใช้ `{page}`/`{pages}` */
|
|
@@ -36,6 +79,13 @@ export interface UiText {
|
|
|
36
79
|
deleting: string;
|
|
37
80
|
deleted: string;
|
|
38
81
|
delete: string;
|
|
82
|
+
search: string;
|
|
83
|
+
clearSearch: string;
|
|
84
|
+
filters: string;
|
|
85
|
+
filtersDescription: string;
|
|
86
|
+
closeFilters: string;
|
|
87
|
+
/** ใช้ `{name}` */
|
|
88
|
+
clearFilter: string;
|
|
39
89
|
}>;
|
|
40
90
|
/** DialogsProvider: ปุ่มปิดของ alert (optional — ค่าเริ่มต้น "ตกลง") */
|
|
41
91
|
ok?: string;
|
|
@@ -153,7 +203,49 @@ export const UI_TEXT_TH: UiText = {
|
|
|
153
203
|
confirmTitle: "ยืนยันการทำรายการ",
|
|
154
204
|
actionFailed: "ดำเนินการไม่สำเร็จ",
|
|
155
205
|
edit: "แก้ไข",
|
|
206
|
+
combobox: {
|
|
207
|
+
search: "ค้นหา {label}…",
|
|
208
|
+
empty: "ไม่พบข้อมูล",
|
|
209
|
+
clearAll: "ล้างทั้งหมด",
|
|
210
|
+
remove: "ลบ {label}",
|
|
211
|
+
add: "เพิ่ม “{value}”",
|
|
212
|
+
},
|
|
213
|
+
filter: {
|
|
214
|
+
title: "กรองตามคุณสมบัติ",
|
|
215
|
+
description: "ค่าในคุณสมบัติเดียวกันเป็น OR และคนละคุณสมบัติเป็น AND",
|
|
216
|
+
searchProperty: "ค้นหาคุณสมบัติ…",
|
|
217
|
+
clearSearch: "ล้างคำค้นหา",
|
|
218
|
+
noProperties: "ไม่พบคุณสมบัติ",
|
|
219
|
+
active: "ตัวกรองคุณสมบัติที่ใช้งาน",
|
|
220
|
+
searchValue: "ค้นหาข้อความ…",
|
|
221
|
+
filterOne: "กรอง {name}",
|
|
222
|
+
clearOne: "ล้างตัวกรอง {name}",
|
|
223
|
+
min: "ต่ำสุด",
|
|
224
|
+
max: "สูงสุด",
|
|
225
|
+
unit: "เลือกหน่วย",
|
|
226
|
+
},
|
|
227
|
+
nav: {
|
|
228
|
+
main: "เมนูหลัก",
|
|
229
|
+
sub: "เมนูย่อย",
|
|
230
|
+
choosePage: "เลือกหน้า…",
|
|
231
|
+
noResults: "ไม่พบ “{query}”",
|
|
232
|
+
clearSearch: "ล้างคำค้นหาเมนู",
|
|
233
|
+
},
|
|
234
|
+
sessionExpired: {
|
|
235
|
+
title: "เซสชันหมดอายุ",
|
|
236
|
+
description:
|
|
237
|
+
"คุณถูกออกจากระบบระหว่างใช้งาน ข้อมูลที่ยังไม่ได้บันทึกจะหายไป กรุณาเข้าสู่ระบบอีกครั้ง แล้วระบบจะพากลับมาหน้านี้",
|
|
238
|
+
signIn: "เข้าสู่ระบบ",
|
|
239
|
+
},
|
|
156
240
|
selection: { selected: "เลือก {count} รายการ", clear: "ล้างการเลือก" },
|
|
241
|
+
sortable: {
|
|
242
|
+
handle: "ลากเพื่อจัดลำดับ",
|
|
243
|
+
instructions: "กด Space เพื่อเริ่มลาก ใช้ปุ่มลูกศรเลื่อน แล้วกด Space อีกครั้งเพื่อวาง (Esc = ยกเลิก)",
|
|
244
|
+
started: "เริ่มลากรายการที่ {index} จาก {total}",
|
|
245
|
+
moved: "ย้ายไปตำแหน่งที่ {index} จาก {total}",
|
|
246
|
+
dropped: "วางที่ตำแหน่งที่ {index} จาก {total}",
|
|
247
|
+
cancelled: "ยกเลิกการลาก",
|
|
248
|
+
},
|
|
157
249
|
table: {
|
|
158
250
|
rowsPerPage: "แถวต่อหน้า",
|
|
159
251
|
pageOf: "หน้า {page} จาก {pages}",
|
|
@@ -164,6 +256,12 @@ export const UI_TEXT_TH: UiText = {
|
|
|
164
256
|
deleting: "กำลังลบ{resource} “{name}”…",
|
|
165
257
|
deleted: "ลบ{resource} “{name}” แล้ว",
|
|
166
258
|
delete: "ลบ",
|
|
259
|
+
search: "ค้นหาข้อมูลในตาราง",
|
|
260
|
+
clearSearch: "ล้างคำค้นหา",
|
|
261
|
+
filters: "ตัวกรองข้อมูล",
|
|
262
|
+
filtersDescription: "เลือกได้หลายเงื่อนไข ผลลัพธ์จะอัปเดตทันที",
|
|
263
|
+
closeFilters: "ปิดตัวกรอง",
|
|
264
|
+
clearFilter: "ล้างตัวกรอง {name}",
|
|
167
265
|
},
|
|
168
266
|
ok: "ตกลง",
|
|
169
267
|
alertTitle: "แจ้งให้ทราบ",
|
|
@@ -264,7 +362,50 @@ export const UI_TEXT_EN: UiText = {
|
|
|
264
362
|
confirmTitle: "Please confirm",
|
|
265
363
|
actionFailed: "Action failed",
|
|
266
364
|
edit: "Edit",
|
|
365
|
+
combobox: {
|
|
366
|
+
search: "Search {label}…",
|
|
367
|
+
empty: "No results",
|
|
368
|
+
clearAll: "Clear all",
|
|
369
|
+
remove: "Remove {label}",
|
|
370
|
+
add: "Add “{value}”",
|
|
371
|
+
},
|
|
372
|
+
filter: {
|
|
373
|
+
title: "Filter by property",
|
|
374
|
+
description: "Values in the same property are OR, different properties are AND",
|
|
375
|
+
searchProperty: "Search properties…",
|
|
376
|
+
clearSearch: "Clear search",
|
|
377
|
+
noProperties: "No properties found",
|
|
378
|
+
active: "Active property filters",
|
|
379
|
+
searchValue: "Search values…",
|
|
380
|
+
filterOne: "Filter {name}",
|
|
381
|
+
clearOne: "Clear {name} filter",
|
|
382
|
+
min: "Min",
|
|
383
|
+
max: "Max",
|
|
384
|
+
unit: "Select unit",
|
|
385
|
+
},
|
|
386
|
+
nav: {
|
|
387
|
+
main: "Main menu",
|
|
388
|
+
sub: "Section menu",
|
|
389
|
+
choosePage: "Choose a page…",
|
|
390
|
+
noResults: "No match for “{query}”",
|
|
391
|
+
clearSearch: "Clear menu search",
|
|
392
|
+
},
|
|
393
|
+
sessionExpired: {
|
|
394
|
+
title: "Session expired",
|
|
395
|
+
description:
|
|
396
|
+
"You were signed out while working. Unsaved changes are lost — sign in again and we will bring you back to this page.",
|
|
397
|
+
signIn: "Sign in",
|
|
398
|
+
},
|
|
267
399
|
selection: { selected: "{count} selected", clear: "Clear selection" },
|
|
400
|
+
sortable: {
|
|
401
|
+
handle: "Drag to reorder",
|
|
402
|
+
instructions:
|
|
403
|
+
"Press Space to start dragging, use arrow keys to move, Space again to drop (Esc to cancel)",
|
|
404
|
+
started: "Picked up item {index} of {total}",
|
|
405
|
+
moved: "Moved to position {index} of {total}",
|
|
406
|
+
dropped: "Dropped at position {index} of {total}",
|
|
407
|
+
cancelled: "Dragging cancelled",
|
|
408
|
+
},
|
|
268
409
|
table: {
|
|
269
410
|
rowsPerPage: "Rows per page",
|
|
270
411
|
pageOf: "Page {page} of {pages}",
|
|
@@ -275,6 +416,12 @@ export const UI_TEXT_EN: UiText = {
|
|
|
275
416
|
deleting: "Deleting{resource} “{name}”…",
|
|
276
417
|
deleted: "“{name}”{resource} deleted",
|
|
277
418
|
delete: "Delete",
|
|
419
|
+
search: "Search the table",
|
|
420
|
+
clearSearch: "Clear search",
|
|
421
|
+
filters: "Filters",
|
|
422
|
+
filtersDescription: "Combine conditions — results update instantly",
|
|
423
|
+
closeFilters: "Close filters",
|
|
424
|
+
clearFilter: "Clear {name} filter",
|
|
278
425
|
},
|
|
279
426
|
ok: "OK",
|
|
280
427
|
alertTitle: "Notice",
|