@comitor/ui 1.3.0 → 1.3.1
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.
|
@@ -5,18 +5,18 @@ import { useId } from "react";
|
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
6
|
import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from "../ui/field.js";
|
|
7
7
|
const colSpanClasses = {
|
|
8
|
-
1: "col-span-
|
|
9
|
-
2: "col-span-
|
|
10
|
-
3: "col-span-
|
|
11
|
-
4: "col-span-
|
|
12
|
-
5: "col-span-
|
|
13
|
-
6: "col-span-
|
|
14
|
-
7: "col-span-
|
|
15
|
-
8: "col-span-
|
|
16
|
-
9: "col-span-
|
|
17
|
-
10: "col-span-
|
|
18
|
-
11: "col-span-
|
|
19
|
-
12: "col-span-
|
|
8
|
+
1: "col-span-full md:col-span-1",
|
|
9
|
+
2: "col-span-full md:col-span-2",
|
|
10
|
+
3: "col-span-full sm:col-span-6 md:col-span-3",
|
|
11
|
+
4: "col-span-full sm:col-span-6 md:col-span-4",
|
|
12
|
+
5: "col-span-full sm:col-span-6 md:col-span-5",
|
|
13
|
+
6: "col-span-full sm:col-span-6",
|
|
14
|
+
7: "col-span-full md:col-span-7",
|
|
15
|
+
8: "col-span-full md:col-span-8",
|
|
16
|
+
9: "col-span-full md:col-span-9",
|
|
17
|
+
10: "col-span-full md:col-span-10",
|
|
18
|
+
11: "col-span-full md:col-span-11",
|
|
19
|
+
12: "col-span-full"
|
|
20
20
|
};
|
|
21
21
|
function FormField({
|
|
22
22
|
label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/composite/form-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle } from \"lucide-react\";\nimport { type ReactNode, useId } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from \"../ui/field.js\";\n\nexport type FormFieldColSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;\n\n/**\n * Thuộc tính a11y mà `FormField` tiêm xuống control con.\n *\n * Trải nguyên cụm này vào `<Input>`/`<Combobox>`/`<Select>` là control tự có id khớp nhãn,\n * tự báo lỗi và trạng thái bắt buộc — không phải nhớ từng thuộc tính ở mỗi trang.\n */\nexport interface FormFieldControlProps {\n id: string;\n \"aria-invalid\"?: true;\n \"aria-required\"?: true;\n \"aria-describedby\"?: string;\n}\n\nexport interface FormFieldProps {\n label?: ReactNode;\n /** Đánh dấu bắt buộc: vẽ dấu * và phát `aria-required` xuống control */\n required?: boolean;\n /** Mô tả/gợi ý dưới trường — bị ẩn khi có lỗi để không đọc chồng lời */\n description?: ReactNode;\n /** Thông báo lỗi; có giá trị là trường vào trạng thái invalid */\n error?: ReactNode;\n /** id của control — bỏ trống thì tự sinh bằng useId */\n htmlFor?: string;\n /** Số cột chiếm trong lưới 12 cột của `FormSection` (mặc định 12 = cả hàng) */\n colSpan?: FormFieldColSpan;\n orientation?: \"vertical\" | \"horizontal\" | \"responsive\";\n className?: string;\n /**\n * Control của trường. Dạng hàm sẽ nhận `FormFieldControlProps` để trải xuống —\n * dạng node thì app tự lo id/aria (dùng khi control đã tự quản, vd Checkbox có nhãn riêng).\n */\n children: ReactNode | ((control: FormFieldControlProps) => ReactNode);\n}\n\nconst colSpanClasses: Record<FormFieldColSpan, string> = {\n 1: \"col-span-
|
|
1
|
+
{"version":3,"sources":["../../../src/components/composite/form-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle } from \"lucide-react\";\nimport { type ReactNode, useId } from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from \"../ui/field.js\";\n\nexport type FormFieldColSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;\n\n/**\n * Thuộc tính a11y mà `FormField` tiêm xuống control con.\n *\n * Trải nguyên cụm này vào `<Input>`/`<Combobox>`/`<Select>` là control tự có id khớp nhãn,\n * tự báo lỗi và trạng thái bắt buộc — không phải nhớ từng thuộc tính ở mỗi trang.\n */\nexport interface FormFieldControlProps {\n id: string;\n \"aria-invalid\"?: true;\n \"aria-required\"?: true;\n \"aria-describedby\"?: string;\n}\n\nexport interface FormFieldProps {\n label?: ReactNode;\n /** Đánh dấu bắt buộc: vẽ dấu * và phát `aria-required` xuống control */\n required?: boolean;\n /** Mô tả/gợi ý dưới trường — bị ẩn khi có lỗi để không đọc chồng lời */\n description?: ReactNode;\n /** Thông báo lỗi; có giá trị là trường vào trạng thái invalid */\n error?: ReactNode;\n /** id của control — bỏ trống thì tự sinh bằng useId */\n htmlFor?: string;\n /** Số cột chiếm trong lưới 12 cột của `FormSection` (mặc định 12 = cả hàng) */\n colSpan?: FormFieldColSpan;\n orientation?: \"vertical\" | \"horizontal\" | \"responsive\";\n className?: string;\n /**\n * Control của trường. Dạng hàm sẽ nhận `FormFieldControlProps` để trải xuống —\n * dạng node thì app tự lo id/aria (dùng khi control đã tự quản, vd Checkbox có nhãn riêng).\n */\n children: ReactNode | ((control: FormFieldControlProps) => ReactNode);\n}\n\n/*\n * ⚠ BẬC BASE PHẢI LÀ `col-span-full`, KHÔNG PHẢI `col-span-12`.\n *\n * Hai class này trông tương đương trong lưới 12 cột của `FormSection` — và đúng là tương đương\n * Ở ĐÓ (đo được: cả hai cho trường rộng đủ một hàng). Nhưng chúng biên dịch ra hai thứ KHÁC HẲN:\n * col-span-12 → grid-column: span 12 / span 12 (một phép ĐẾM tương đối)\n * col-span-full → grid-column: 1 / -1 (từ MÉP tới MÉP của lưới)\n *\n * `span 12` trong một `display:grid` KHÔNG khai `grid-cols-*` buộc trình duyệt ĐẺ 11 track NGẦM\n * để chứa cho đủ. `DialogContent` của gói chính là một grid như vậy (`grid ... gap-4 p-6`), nên\n * một `FormField` đặt thẳng trong hộp thoại làm cả hộp vỡ theo HAI cách cùng lúc:\n * - trường THÒ RA ngoài mép hộp: nó rộng bằng (track 1 + 11 khe);\n * - MỌI anh em không span — `DialogHeader`, `DialogFooter` — bị bóp về track 1, nên mô tả\n * xuống dòng sớm và các nút dồn lại.\n * Đo trên bản thật (hộp 432px, vùng nội dung 378px, gap 18px): 12 track `217.43px 0px ×11`,\n * trường 415.43px, thò 37.43px, header co còn 217.43px.\n *\n * `1 / -1` không đếm gì cả nên nó KHÔNG đẻ track: cùng phép đo cho 1 track, cả ba con đều 378px.\n * Và trong `grid-cols-12` thật thì `1 / -1` = cả 12 cột, tức KHÔNG đổi một pixel nào ở đường\n * dùng đúng. Vì vậy đây là bản vá không phá vỡ tương thích, không phải một đánh đổi.\n *\n * ⚠ Các bậc `sm:`/`md:` thì PHẢI giữ `col-span-<số>` — chúng là phép đếm thật trong lưới 12 cột,\n * và `1 / -1` sẽ biến `colSpan={6}` thành cả hàng. Đổi lại, một app khai `colSpan` tường minh\n * BÊN TRONG một grid không phải 12 cột vẫn đẻ track ngầm từ breakpoint đó trở lên — nhưng đó là\n * lựa chọn tường minh của chỗ gọi, không phải cái bẫy im lặng mà mặc định `12` từng giăng ra.\n *\n * `scripts/check-grid-contract.mjs` khoá luật này lại.\n */\nconst colSpanClasses: Record<FormFieldColSpan, string> = {\n 1: \"col-span-full md:col-span-1\",\n 2: \"col-span-full md:col-span-2\",\n 3: \"col-span-full sm:col-span-6 md:col-span-3\",\n 4: \"col-span-full sm:col-span-6 md:col-span-4\",\n 5: \"col-span-full sm:col-span-6 md:col-span-5\",\n 6: \"col-span-full sm:col-span-6\",\n 7: \"col-span-full md:col-span-7\",\n 8: \"col-span-full md:col-span-8\",\n 9: \"col-span-full md:col-span-9\",\n 10: \"col-span-full md:col-span-10\",\n 11: \"col-span-full md:col-span-11\",\n 12: \"col-span-full\"\n};\n\n/**\n * Một trường form: nhãn + control + mô tả/lỗi, đặt trong lưới 12 cột của `FormSection`.\n *\n * KHÔNG tự vẽ input. Nhãn và lỗi uỷ quyền xuống primitive `Field`, còn control do app chọn —\n * nếu composite tự dựng input thì mỗi kiểu control mới (combobox, date range, editor) lại phải\n * mở lại file này — một FormField \"đa năng\" ôm hết mọi loại control sẽ phình ra không có điểm dừng.\n */\nexport function FormField({\n label,\n required = false,\n description,\n error,\n htmlFor,\n colSpan = 12,\n orientation = \"vertical\",\n className,\n children\n}: FormFieldProps) {\n const generatedId = useId();\n const id = htmlFor ?? generatedId;\n const hasError = Boolean(error);\n const errorId = `${id}-error`;\n const descriptionId = `${id}-description`;\n\n const control: FormFieldControlProps = {\n id,\n \"aria-invalid\": hasError ? true : undefined,\n \"aria-required\": required ? true : undefined,\n \"aria-describedby\": hasError ? errorId : description ? descriptionId : undefined\n };\n\n return (\n <Field\n data-slot=\"form-field\"\n data-invalid={hasError || undefined}\n orientation={orientation}\n className={cn(colSpanClasses[colSpan], className)}\n >\n {/*\n Nhãn KHÔNG truyền `className=\"text-sm\"`: `Label` bên dưới đã đặt sẵn `text-sm` nên chuỗi đó\n không mua được gì — mà lại làm mất HAI chiều cao dòng. `cn()` chạy qua tailwind-merge, ở đó\n một utility CỠ CHỮ xoá mọi `leading-*` đứng TRƯỚC nó: `text-sm` nằm cuối chuỗi cắt\n `leading-snug` của `FieldLabel`, rồi cắt tiếp `leading-none` trong base của `Label`. Kết quả\n là thẻ <label> không còn `leading-*` nào.\n */}\n {label && (\n <FieldLabel htmlFor={id}>\n {label}\n {required && (\n // Dấu * chỉ là tín hiệu thị giác — trạng thái bắt buộc đã nằm ở aria-required trên\n // control, để trình đọc màn hình khỏi đọc thừa \"sao\".\n // Bậc ink (7,15:1 light / 6,42:1 dark trên `--card`) chứ không `text-destructive`\n // (bậc NỀN, chỉ 3,53:1 trên `--card` ở dark ✗).\n <span aria-hidden=\"true\" className=\"text-destructive-ink\">\n *\n </span>\n )}\n </FieldLabel>\n )}\n\n <FieldContent>\n {typeof children === \"function\" ? children(control) : children}\n\n {hasError ? (\n <FieldError id={errorId} className=\"flex items-center gap-1.5 text-xs animate-in fade-in slide-in-from-top-1\">\n <AlertCircle aria-hidden=\"true\" className=\"size-3.5 shrink-0\" />\n {error}\n </FieldError>\n ) : (\n description && (\n // `leading-normal` phải VIẾT LẠI sau `text-xs`: cùng cái bẫy tailwind-merge ở nhãn phía\n // trên — `text-xs` xoá `leading-normal` trong base của `FieldDescription` và kéo dòng\n // mô tả từ 1.5 xuống 1rem. Cỡ chữ nhỏ hơn base là CÓ Ý, chiều cao dòng thì không.\n <FieldDescription id={descriptionId} className=\"text-xs leading-normal\">\n {description}\n </FieldDescription>\n )\n )}\n </FieldContent>\n </Field>\n );\n}\n"],"mappings":";AAoIQ,SAOI,KAPJ;AAlIR,SAAS,mBAAmB;AAC5B,SAAyB,aAAa;AACtC,SAAS,UAAU;AACnB,SAAS,OAAO,cAAc,kBAAkB,YAAY,kBAAkB;AAkE9E,MAAM,iBAAmD;AAAA,EACvD,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AASO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,cAAc,MAAM;AAC1B,QAAM,KAAK,WAAW;AACtB,QAAM,WAAW,QAAQ,KAAK;AAC9B,QAAM,UAAU,GAAG,EAAE;AACrB,QAAM,gBAAgB,GAAG,EAAE;AAE3B,QAAM,UAAiC;AAAA,IACrC;AAAA,IACA,gBAAgB,WAAW,OAAO;AAAA,IAClC,iBAAiB,WAAW,OAAO;AAAA,IACnC,oBAAoB,WAAW,UAAU,cAAc,gBAAgB;AAAA,EACzE;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,YAAY;AAAA,MAC1B;AAAA,MACA,WAAW,GAAG,eAAe,OAAO,GAAG,SAAS;AAAA,MAS/C;AAAA,iBACC,qBAAC,cAAW,SAAS,IAClB;AAAA;AAAA,UACA;AAAA;AAAA;AAAA;AAAA,UAKC,oBAAC,UAAK,eAAY,QAAO,WAAU,wBAAuB,eAE1D;AAAA,WAEJ;AAAA,QAGF,qBAAC,gBACE;AAAA,iBAAO,aAAa,aAAa,SAAS,OAAO,IAAI;AAAA,UAErD,WACC,qBAAC,cAAW,IAAI,SAAS,WAAU,4EACjC;AAAA,gCAAC,eAAY,eAAY,QAAO,WAAU,qBAAoB;AAAA,YAC7D;AAAA,aACH,IAEA;AAAA;AAAA;AAAA,UAIE,oBAAC,oBAAiB,IAAI,eAAe,WAAU,0BAC5C,uBACH;AAAA,WAGN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comitor/ui",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"verify:directives": "node scripts/verify-directives.mjs",
|
|
69
69
|
"smoke:entries": "node scripts/smoke-entries.mjs",
|
|
70
70
|
"check:exports": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css",
|
|
71
|
-
"prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives && pnpm run smoke:entries && pnpm run check:exports && pnpm run test:nav-active && pnpm run test:a11y-gate && pnpm run lint:a11y"
|
|
71
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run build && pnpm run verify:directives && pnpm run smoke:entries && pnpm run check:exports && pnpm run test:nav-active && pnpm run test:grid-contract && pnpm run test:a11y-gate && pnpm run lint:a11y",
|
|
72
|
+
"test:grid-contract": "node scripts/check-grid-contract.mjs"
|
|
72
73
|
},
|
|
73
74
|
"peerDependencies": {
|
|
74
75
|
"@uppy/core": "^6.0.0",
|