lily-lark-ui 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/components/alert-dialog.d.ts +2 -1
  3. package/dist/components/alert-dialog.js +7 -2
  4. package/dist/components/checkbox.d.ts +4 -2
  5. package/dist/components/checkbox.js +6 -2
  6. package/dist/components/dialog.d.ts +7 -13
  7. package/dist/components/dialog.js +6 -6
  8. package/dist/components/dropdown-menu.d.ts +16 -8
  9. package/dist/components/dropdown-menu.js +5 -5
  10. package/dist/components/empty-state.d.ts +2 -3
  11. package/dist/components/empty-state.js +2 -2
  12. package/dist/components/pagination.d.ts +1 -1
  13. package/dist/components/pagination.js +2 -2
  14. package/dist/components/radio-group.d.ts +7 -3
  15. package/dist/components/radio-group.js +6 -4
  16. package/dist/components/select.d.ts +5 -1
  17. package/dist/components/select.js +3 -2
  18. package/dist/components/spinner.d.ts +3 -2
  19. package/dist/components/spinner.js +2 -2
  20. package/dist/components/switch.d.ts +3 -1
  21. package/dist/components/switch.js +4 -3
  22. package/dist/components/table.d.ts +15 -13
  23. package/dist/components/table.js +10 -10
  24. package/dist/components/tabs.d.ts +14 -18
  25. package/dist/components/tabs.js +6 -6
  26. package/dist/components/toast.d.ts +7 -3
  27. package/dist/components/toast.js +3 -3
  28. package/dist/components/tooltip.d.ts +6 -6
  29. package/dist/components/tooltip.js +3 -3
  30. package/lark-ui-components.html +1 -1
  31. package/package.json +1 -1
  32. package/src/components/alert-dialog.tsx +19 -2
  33. package/src/components/checkbox.tsx +37 -20
  34. package/src/components/dialog.tsx +15 -21
  35. package/src/components/dropdown-menu.tsx +31 -17
  36. package/src/components/empty-state.tsx +7 -4
  37. package/src/components/pagination.tsx +4 -2
  38. package/src/components/radio-group.tsx +37 -15
  39. package/src/components/select.tsx +32 -6
  40. package/src/components/spinner.tsx +6 -2
  41. package/src/components/switch.tsx +18 -5
  42. package/src/components/table.tsx +54 -20
  43. package/src/components/tabs.tsx +24 -43
  44. package/src/components/toast.tsx +24 -6
  45. package/src/components/tooltip.tsx +19 -11
package/CHANGELOG.md CHANGED
@@ -6,6 +6,35 @@ Mọi thay đổi đáng chú ý của `lily-lark-ui` được ghi ở đây. Đ
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Added — prop HTML trên toàn bộ component
10
+
11
+ 12 component từng chỉ nhận vài prop (Select, Switch, Checkbox, RadioGroup,
12
+ Pagination, Tabs, Dialog, AlertDialog, DropdownMenu, Tooltip, Spinner,
13
+ EmptyState, Table) nay nhận đủ prop HTML của phần tử gốc: `onBlur`, `onFocus`,
14
+ `onKeyDown`, `name`, `form`, `value`, `data-*`, `aria-*`, `style`, `id`, `ref`.
15
+
16
+ - Interface kế thừa `ComponentPropsWithoutRef<typeof Primitive>` /
17
+ `HTMLAttributes<...>` thay vì khai báo tay vài prop.
18
+ - Thêm prop phụ để không phá API cũ: `controlClassName` (Switch, Checkbox,
19
+ Radio.Item), `containerClassName` (Select), `triggerClassName` (Tooltip),
20
+ `toastClassName`/`viewportClassName` (ToastProvider).
21
+ - `LarkTable` thêm `onRowClick(row, index)`, `rowClassName(row, index)`,
22
+ `ref` tới `<table>`; `LarkColumn` thêm `className`/`headerClassName`.
23
+ - `LarkRadioGroup` nhận `children` (dùng `LarkRadioGroup.Item` tự bố trí) song
24
+ song với `options`.
25
+ - `LarkTooltip.content` nhận `ReactNode` (trước là string).
26
+
27
+ ### Fixed
28
+
29
+ - `LarkEmptyState` và `LarkSpinner` merge `className` qua `cn` nên class riêng
30
+ override được class kích thước (trước ghi đè mất `size-4`/`size-6`).
31
+ - `AlertDialog.onEscapeKeyDown` do người dùng truyền không còn bị bỏ qua khi
32
+ `isPending` (giờ chặn trước rồi gọi handler của người dùng).
33
+ - `tests/prop-passthrough.test.tsx` (15 test) + fixture kiểu
34
+ `tests/fixtures/types-props.tsx` chạy `tsc` để chặn hồi quy.
35
+ - `vitest.setup.ts` mock `ResizeObserver` cho Radix.
36
+
37
+
9
38
  ## [0.2.0] — 2026-11-05
10
39
 
11
40
  ### Changed — đổi tên package sang `lily-lark-ui`
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- export interface LarkAlertDialogProps {
2
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
3
+ export interface LarkAlertDialogProps extends Omit<React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>, 'title' | 'children' | 'className'> {
3
4
  open?: boolean;
4
5
  onOpenChange?: (open: boolean) => void;
5
6
  title: string;
@@ -4,8 +4,13 @@ import * as React from 'react';
4
4
  import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
5
5
  import { cn } from '../lib/utils.js';
6
6
  import { LarkButton } from './button.js';
7
- function LarkAlertDialogBase({ open, onOpenChange, title, description, confirmLabel, cancelLabel = 'Hủy', onConfirm, isPending, children, className, }, ref) {
8
- return (_jsx(AlertDialogPrimitive.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { className: "fixed inset-0 bg-[var(--overlay-mask)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out" }), _jsxs(AlertDialogPrimitive.Content, { ref: ref, ...(description ? {} : { 'aria-describedby': undefined }), onEscapeKeyDown: isPending ? (event) => event.preventDefault() : undefined, className: cn('fixed left-1/2 top-1/2 w-[calc(100vw-2.5rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-brand-primary p-5 shadow-[var(--shadow-overlay)] outline-none data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out md:w-full md:max-w-md md:p-6', className), children: [_jsx(AlertDialogPrimitive.Title, { className: "text-base font-medium text-lark-n900 md:text-[17px]", children: title }), description ? (_jsx(AlertDialogPrimitive.Description, { className: "mt-3 text-sm text-lark-n600", children: description })) : null, children ? _jsx("div", { className: "mt-3", children: children }) : null, _jsxs("div", { className: "mt-3 flex justify-end gap-3 border-t border-[color-mix(in_srgb,var(--color-lark-n900)_15%,transparent)] px-5 py-4 md:px-6 -mx-5 -mb-5 md:-mx-6 md:-mb-6", children: [_jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: _jsx(LarkButton, { type: "button", disabled: isPending, children: cancelLabel }) }), _jsx(AlertDialogPrimitive.Action, { asChild: true, children: _jsx(LarkButton, { type: "button", variant: "primary", isPending: isPending, onClick: onConfirm, children: confirmLabel }) })] })] })] }) }));
7
+ function LarkAlertDialogBase({ open, onOpenChange, title, description, confirmLabel, cancelLabel = 'Hủy', onConfirm, isPending, children, className, onEscapeKeyDown, ...props }, ref) {
8
+ return (_jsx(AlertDialogPrimitive.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(AlertDialogPrimitive.Portal, { children: [_jsx(AlertDialogPrimitive.Overlay, { className: "fixed inset-0 bg-[var(--overlay-mask)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out" }), _jsxs(AlertDialogPrimitive.Content, { ref: ref, ...(description ? {} : { 'aria-describedby': undefined }), onEscapeKeyDown: isPending
9
+ ? (event) => {
10
+ event.preventDefault();
11
+ onEscapeKeyDown?.(event);
12
+ }
13
+ : onEscapeKeyDown, className: cn('fixed left-1/2 top-1/2 w-[calc(100vw-2.5rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-brand-primary p-5 shadow-[var(--shadow-overlay)] outline-none data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out md:w-full md:max-w-md md:p-6', className), ...props, children: [_jsx(AlertDialogPrimitive.Title, { className: "text-base font-medium text-lark-n900 md:text-[17px]", children: title }), description ? (_jsx(AlertDialogPrimitive.Description, { className: "mt-3 text-sm text-lark-n600", children: description })) : null, children ? _jsx("div", { className: "mt-3", children: children }) : null, _jsxs("div", { className: "mt-3 flex justify-end gap-3 border-t border-[color-mix(in_srgb,var(--color-lark-n900)_15%,transparent)] px-5 py-4 md:px-6 -mx-5 -mb-5 md:-mx-6 md:-mb-6", children: [_jsx(AlertDialogPrimitive.Cancel, { asChild: true, children: _jsx(LarkButton, { type: "button", disabled: isPending, children: cancelLabel }) }), _jsx(AlertDialogPrimitive.Action, { asChild: true, children: _jsx(LarkButton, { type: "button", variant: "primary", isPending: isPending, onClick: onConfirm, children: confirmLabel }) })] })] })] }) }));
9
14
  }
10
15
  const LarkAlertDialogInner = React.forwardRef(LarkAlertDialogBase);
11
16
  LarkAlertDialogInner.displayName = 'LarkAlertDialog';
@@ -1,10 +1,12 @@
1
1
  import * as React from 'react';
2
- export interface LarkCheckboxProps {
3
- label: string;
2
+ import * as Checkbox from '@radix-ui/react-checkbox';
3
+ export interface LarkCheckboxProps extends Omit<React.ComponentPropsWithoutRef<typeof Checkbox.Root>, 'checked' | 'onCheckedChange' | 'disabled' | 'className'> {
4
+ label?: string;
4
5
  checked?: boolean | 'indeterminate';
5
6
  onCheckedChange?: (checked: boolean | 'indeterminate') => void;
6
7
  disabled?: boolean;
7
8
  className?: string;
9
+ controlClassName?: string;
8
10
  }
9
11
  declare const LarkCheckboxInner: React.ForwardRefExoticComponent<LarkCheckboxProps & React.RefAttributes<HTMLButtonElement>>;
10
12
  export { LarkCheckboxInner as LarkCheckbox };
@@ -5,10 +5,14 @@ import * as Checkbox from '@radix-ui/react-checkbox';
5
5
  import * as Label from '@radix-ui/react-label';
6
6
  import { Check, Minus } from 'lucide-react';
7
7
  import { cn } from '../lib/utils.js';
8
- function LarkCheckboxBase({ label, checked, onCheckedChange, disabled, className }, ref) {
8
+ function LarkCheckboxBase({ label, checked, onCheckedChange, disabled, className, controlClassName, id, ...props }, ref) {
9
9
  const autoId = React.useId();
10
+ const checkboxId = id ?? autoId;
10
11
  const indeterminate = checked === 'indeterminate';
11
- return (_jsxs("div", { className: cn('flex items-center gap-2', className), children: [_jsx(Checkbox.Root, { ref: ref, id: autoId, checked: checked, onCheckedChange: onCheckedChange, disabled: disabled, className: "size-4 shrink-0 rounded-[4px] border border-lark-n500 bg-brand-primary focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:border-brand-secondary data-[state=checked]:bg-brand-secondary data-[state=indeterminate]:border-brand-secondary data-[state=indeterminate]:bg-brand-secondary disabled:cursor-not-allowed disabled:opacity-50", children: _jsx(Checkbox.Indicator, { className: "flex items-center justify-center", children: indeterminate ? (_jsx(Minus, { className: "size-3 text-white", "aria-hidden": "true" })) : (_jsx(Check, { className: "size-3 text-white", "aria-hidden": "true" })) }) }), _jsx(Label.Root, { htmlFor: autoId, className: "text-sm text-lark-n900", children: label })] }));
12
+ const root = (_jsx(Checkbox.Root, { ref: ref, id: checkboxId, checked: checked, onCheckedChange: onCheckedChange, disabled: disabled, className: cn('size-4 shrink-0 rounded-[4px] border border-lark-n500 bg-brand-primary focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:border-brand-secondary data-[state=checked]:bg-brand-secondary data-[state=indeterminate]:border-brand-secondary data-[state=indeterminate]:bg-brand-secondary disabled:cursor-not-allowed disabled:opacity-50', controlClassName), ...props, children: _jsx(Checkbox.Indicator, { className: "flex items-center justify-center", children: indeterminate ? (_jsx(Minus, { className: "size-3 text-white", "aria-hidden": "true" })) : (_jsx(Check, { className: "size-3 text-white", "aria-hidden": "true" })) }) }));
13
+ if (!label)
14
+ return _jsx("div", { className: className, children: root });
15
+ return (_jsxs("div", { className: cn('flex items-center gap-2', className), children: [root, _jsx(Label.Root, { htmlFor: checkboxId, className: "text-sm text-lark-n900", children: label })] }));
12
16
  }
13
17
  const LarkCheckboxInner = React.forwardRef(LarkCheckboxBase);
14
18
  LarkCheckboxInner.displayName = 'LarkCheckbox';
@@ -1,23 +1,17 @@
1
1
  import * as React from 'react';
2
- export interface LarkDialogProps {
3
- open?: boolean;
4
- onOpenChange?: (open: boolean) => void;
5
- children: React.ReactNode;
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
3
+ export interface LarkDialogProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Root> {
6
4
  }
7
- export interface LarkDialogContentProps {
5
+ export interface LarkDialogContentProps extends Omit<React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, 'title'> {
8
6
  title: string;
9
7
  description?: string;
10
- className?: string;
11
- children?: React.ReactNode;
12
8
  }
13
- export interface LarkDialogFooterProps {
14
- children: React.ReactNode;
15
- className?: string;
9
+ export interface LarkDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {
16
10
  }
17
- declare function LarkDialogRoot({ open, onOpenChange, children }: LarkDialogProps): React.JSX.Element;
18
- declare function LarkDialogFooter({ children, className }: LarkDialogFooterProps): React.JSX.Element;
11
+ declare function LarkDialogRoot({ children, ...props }: LarkDialogProps): React.JSX.Element;
12
+ declare function LarkDialogFooter({ children, className, ...props }: LarkDialogFooterProps): React.JSX.Element;
19
13
  export declare const LarkDialog: typeof LarkDialogRoot & {
20
- Content: React.FC<LarkDialogContentProps>;
14
+ Content: React.ForwardRefExoticComponent<LarkDialogContentProps & React.RefAttributes<HTMLDivElement>>;
21
15
  Footer: typeof LarkDialogFooter;
22
16
  };
23
17
  export {};
@@ -3,16 +3,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from 'react';
4
4
  import * as DialogPrimitive from '@radix-ui/react-dialog';
5
5
  import { cn } from '../lib/utils.js';
6
- function LarkDialogRoot({ open, onOpenChange, children }) {
7
- return (_jsx(DialogPrimitive.Root, { open: open, onOpenChange: onOpenChange, children: children }));
6
+ function LarkDialogRoot({ children, ...props }) {
7
+ return _jsx(DialogPrimitive.Root, { ...props, children: children });
8
8
  }
9
- function LarkDialogContentBase({ title, description, className, children }, ref) {
10
- return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 bg-[var(--overlay-mask)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out" }), _jsxs(DialogPrimitive.Content, { ref: ref, ...(description ? {} : { 'aria-describedby': undefined }), className: cn('fixed left-1/2 top-1/2 w-[calc(100vw-2.5rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-brand-primary p-5 shadow-[var(--shadow-overlay)] outline-none data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out md:w-full md:max-w-md md:p-6', className), children: [_jsx(DialogPrimitive.Title, { className: "text-base font-medium text-lark-n900 md:text-[17px]", children: title }), description ? (_jsx(DialogPrimitive.Description, { className: "mt-3 text-sm text-lark-n600", children: description })) : null, children] })] }));
9
+ function LarkDialogContentBase({ title, description, className, children, ...props }, ref) {
10
+ return (_jsxs(DialogPrimitive.Portal, { children: [_jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 bg-[var(--overlay-mask)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out" }), _jsxs(DialogPrimitive.Content, { ref: ref, ...(description ? {} : { 'aria-describedby': undefined }), className: cn('fixed left-1/2 top-1/2 w-[calc(100vw-2.5rem)] -translate-x-1/2 -translate-y-1/2 rounded-xl bg-brand-primary p-5 shadow-[var(--shadow-overlay)] outline-none data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out md:w-full md:max-w-md md:p-6', className), ...props, children: [_jsx(DialogPrimitive.Title, { className: "text-base font-medium text-lark-n900 md:text-[17px]", children: title }), description ? (_jsx(DialogPrimitive.Description, { className: "mt-3 text-sm text-lark-n600", children: description })) : null, children] })] }));
11
11
  }
12
12
  const LarkDialogContentInner = React.forwardRef(LarkDialogContentBase);
13
13
  LarkDialogContentInner.displayName = 'LarkDialog.Content';
14
- function LarkDialogFooter({ children, className }) {
15
- return (_jsx("div", { className: cn('flex justify-end gap-3 border-t border-[color-mix(in_srgb,var(--color-lark-n900)_15%,transparent)] px-5 py-4 md:px-6 -mx-5 -mb-5 md:-mx-6 md:-mb-6', className), children: children }));
14
+ function LarkDialogFooter({ children, className, ...props }) {
15
+ return (_jsx("div", { className: cn('flex justify-end gap-3 border-t border-[color-mix(in_srgb,var(--color-lark-n900)_15%,transparent)] px-5 py-4 md:px-6 -mx-5 -mb-5 md:-mx-6 md:-mb-6', className), ...props, children: children }));
16
16
  }
17
17
  export const LarkDialog = Object.assign(LarkDialogRoot, {
18
18
  Content: LarkDialogContentInner,
@@ -1,11 +1,12 @@
1
1
  import * as React from 'react';
2
- export interface LarkDropdownMenuProps {
2
+ import * as Menu from '@radix-ui/react-menu';
3
+ export interface LarkDropdownMenuProps extends Omit<React.ComponentPropsWithoutRef<typeof Menu.Content>, 'children' | 'className'> {
3
4
  trigger: React.ReactNode;
4
5
  align?: 'start' | 'center' | 'end';
5
6
  children: React.ReactNode;
6
7
  className?: string;
7
8
  }
8
- export interface LarkDropdownMenuItemProps {
9
+ export interface LarkDropdownMenuItemProps extends Omit<React.ComponentPropsWithoutRef<typeof Menu.Item>, 'onSelect' | 'disabled' | 'className'> {
9
10
  value: string;
10
11
  onSelect?: (value: string) => void;
11
12
  danger?: boolean;
@@ -13,12 +14,19 @@ export interface LarkDropdownMenuItemProps {
13
14
  /** Mục đang chọn: icon check + chữ brand (nền trắng theo Figma). Radix Menu.Item không sinh data-state=checked nên dùng prop thay thế. */
14
15
  checked?: boolean;
15
16
  children: React.ReactNode;
16
- }
17
- export interface LarkDropdownMenuSeparatorProps {
18
17
  className?: string;
19
18
  }
20
- export declare const LarkDropdownMenu: React.FC<LarkDropdownMenuProps> & {
21
- Item: React.FC<LarkDropdownMenuItemProps>;
22
- ItemIndicator: React.FC;
23
- Separator: React.FC<LarkDropdownMenuSeparatorProps>;
19
+ declare const LarkDropdownMenuRoot: React.ForwardRefExoticComponent<LarkDropdownMenuProps & React.RefAttributes<HTMLDivElement>>;
20
+ declare function LarkDropdownMenuItem({ value, onSelect, danger, disabled, checked, children, className, ...props }: LarkDropdownMenuItemProps): React.JSX.Element;
21
+ declare const LarkDropdownMenuItemIndicator: React.FC<{
22
+ children?: React.ReactNode;
23
+ }>;
24
+ export interface LarkDropdownMenuSeparatorProps extends React.ComponentPropsWithoutRef<typeof Menu.Separator> {
25
+ }
26
+ declare const LarkDropdownMenuSeparator: React.ForwardRefExoticComponent<LarkDropdownMenuSeparatorProps & React.RefAttributes<HTMLDivElement>>;
27
+ export declare const LarkDropdownMenu: typeof LarkDropdownMenuRoot & {
28
+ Item: typeof LarkDropdownMenuItem;
29
+ ItemIndicator: typeof LarkDropdownMenuItemIndicator;
30
+ Separator: typeof LarkDropdownMenuSeparator;
24
31
  };
32
+ export {};
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import * as Menu from '@radix-ui/react-menu';
5
5
  import { Check } from 'lucide-react';
6
6
  import { cn } from '../lib/utils.js';
7
- function LarkDropdownMenuRootBase({ trigger, align = 'start', children, className }, ref) {
7
+ function LarkDropdownMenuRootBase({ trigger, align = 'start', children, className, ...props }, ref) {
8
8
  // @radix-ui/react-menu (gốc headless) không export Trigger như
9
9
  // @radix-ui/react-dropdown-menu; trigger gắn trực tiếp vào children qua
10
10
  // Menu.Anchor asChild + controlled open, hành vi tương đương Trigger asChild.
@@ -40,17 +40,17 @@ function LarkDropdownMenuRootBase({ trigger, align = 'start', children, classNam
40
40
  },
41
41
  })
42
42
  : (_jsx("span", { ...triggerProps, children: trigger }));
43
- return (_jsxs(Menu.Root, { open: open, onOpenChange: setOpen, modal: false, children: [_jsx(Menu.Anchor, { asChild: true, children: triggerNode }), _jsx(Menu.Portal, { children: _jsx(Menu.Content, { ref: ref, align: align, className: cn('min-w-[200px] rounded-lg border border-lark-n200 bg-brand-primary p-1 shadow-[var(--shadow-overlay)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out', className), children: children }) })] }));
43
+ return (_jsxs(Menu.Root, { open: open, onOpenChange: setOpen, modal: false, children: [_jsx(Menu.Anchor, { asChild: true, children: triggerNode }), _jsx(Menu.Portal, { children: _jsx(Menu.Content, { ref: ref, align: align, className: cn('min-w-[200px] rounded-lg border border-lark-n200 bg-brand-primary p-1 shadow-[var(--shadow-overlay)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out', className), ...props, children: children }) })] }));
44
44
  }
45
45
  const LarkDropdownMenuRoot = React.forwardRef(LarkDropdownMenuRootBase);
46
46
  LarkDropdownMenuRoot.displayName = 'LarkDropdownMenu';
47
- function LarkDropdownMenuItem({ value, onSelect, danger, disabled, checked, children }) {
48
- return (_jsxs(Menu.Item, { disabled: disabled, onSelect: () => onSelect?.(value), "data-checked": checked ? 'true' : undefined, className: cn('flex h-8 items-center gap-2 rounded-lg px-3 text-sm outline-none data-[highlighted]:bg-lark-n100', checked && 'font-medium text-brand-link', danger && 'text-lark-danger'), children: [checked ? _jsx(Check, { className: "size-4 shrink-0" }) : _jsx("span", { className: "size-4 shrink-0", "aria-hidden": true }), children] }));
47
+ function LarkDropdownMenuItem({ value, onSelect, danger, disabled, checked, children, className, ...props }) {
48
+ return (_jsxs(Menu.Item, { disabled: disabled, onSelect: () => onSelect?.(value), "data-checked": checked ? 'true' : undefined, className: cn('flex h-8 items-center gap-2 rounded-lg px-3 text-sm outline-none data-[highlighted]:bg-lark-n100', checked && 'font-medium text-brand-link', danger && 'text-lark-danger', className), ...props, children: [checked ? _jsx(Check, { className: "size-4 shrink-0" }) : _jsx("span", { className: "size-4 shrink-0", "aria-hidden": true }), children] }));
49
49
  }
50
50
  // Chỉ dùng được bên trong Menu.CheckboxItem/RadioItem (nơi Radix sinh
51
51
  // data-state=checked); trong LarkDropdownMenu.Item dùng prop checked thay thế.
52
52
  const LarkDropdownMenuItemIndicator = ({ children }) => (_jsx(Menu.ItemIndicator, { children: children }));
53
- const LarkDropdownMenuSeparatorBase = ({ className }, ref) => (_jsx(Menu.Separator, { ref: ref, className: cn('h-px bg-lark-n200', className) }));
53
+ const LarkDropdownMenuSeparatorBase = ({ className, ...props }, ref) => _jsx(Menu.Separator, { ref: ref, className: cn('h-px bg-lark-n200', className), ...props });
54
54
  const LarkDropdownMenuSeparator = React.forwardRef(LarkDropdownMenuSeparatorBase);
55
55
  LarkDropdownMenuSeparator.displayName = 'LarkDropdownMenu.Separator';
56
56
  export const LarkDropdownMenu = Object.assign(LarkDropdownMenuRoot, {
@@ -1,9 +1,8 @@
1
1
  import * as React from 'react';
2
- export interface LarkEmptyStateProps {
2
+ export interface LarkEmptyStateProps extends React.HTMLAttributes<HTMLDivElement> {
3
3
  icon?: React.ReactNode;
4
4
  title: string;
5
5
  description?: string;
6
6
  action?: React.ReactNode;
7
- className?: string;
8
7
  }
9
- export declare function LarkEmptyState({ icon, title, description, action, className }: LarkEmptyStateProps): React.JSX.Element;
8
+ export declare function LarkEmptyState({ icon, title, description, action, className, ...props }: LarkEmptyStateProps): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../lib/utils.js';
3
- export function LarkEmptyState({ icon, title, description, action, className }) {
4
- return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 py-10 text-center text-sm text-lark-n600', className), children: [icon && _jsx("span", { className: "text-lark-n400 [&_svg]:size-6", children: icon }), _jsx("p", { className: "font-medium text-lark-n700", children: title }), description && _jsx("p", { className: "text-lark-n600", children: description }), action && _jsx("div", { className: "mt-2", children: action })] }));
3
+ export function LarkEmptyState({ icon, title, description, action, className, ...props }) {
4
+ return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 py-10 text-center text-sm text-lark-n600', className), ...props, children: [icon && _jsx("span", { className: "text-lark-n400 [&_svg]:size-6", children: icon }), _jsx("p", { className: "font-medium text-lark-n700", children: title }), description && _jsx("p", { className: "text-lark-n600", children: description }), action && _jsx("div", { className: "mt-2", children: action })] }));
5
5
  }
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  export declare const DEFAULT_PAGE_SIZES: number[];
3
3
  export type PageWindowItem = number | 'ellipsis';
4
4
  export declare function pageWindow(page: number, totalPages: number): PageWindowItem[];
5
- export interface LarkPaginationProps {
5
+ export interface LarkPaginationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
6
6
  page: number;
7
7
  total: number;
8
8
  pageSize: number;
@@ -20,10 +20,10 @@ export function pageWindow(page, totalPages) {
20
20
  return [1, 'ellipsis', page - 1, page, page + 1, 'ellipsis', totalPages];
21
21
  }
22
22
  const pageButtonClass = 'inline-flex size-7 items-center justify-center rounded-lg text-sm text-brand-link hover:bg-lark-n50';
23
- function LarkPaginationBase({ page, total, pageSize, onPageChange, pageSizes = DEFAULT_PAGE_SIZES, onPageSizeChange, isLoading, className, }, ref) {
23
+ function LarkPaginationBase({ page, total, pageSize, onPageChange, pageSizes = DEFAULT_PAGE_SIZES, onPageSizeChange, isLoading, className, ...props }, ref) {
24
24
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
25
25
  const items = pageWindow(page, totalPages);
26
- return (_jsxs("div", { ref: ref, className: cn('flex flex-wrap items-center justify-end gap-4', className), children: [isLoading ? _jsx(LarkSpinner, { size: "sm", label: "\u0110ang t\u1EA3i d\u1EEF li\u1EC7u" }) : null, _jsxs("nav", { "aria-label": "Ph\u00E2n trang", className: "flex items-center gap-1", children: [_jsx(LarkButton, { isIconOnly: true, variant: "ghost", size: "sm", "aria-label": "Trang tr\u01B0\u1EDBc", disabled: page <= 1, onClick: () => onPageChange(page - 1), children: _jsx(ChevronLeft, { className: "size-4", strokeWidth: 2.4 }) }), items.map((item, index) => item === 'ellipsis' ? (_jsx("span", { "aria-hidden": "true", className: "px-1 text-lark-n400", children: "\u2026" }, `gap-${index}`)) : (_jsx("button", { type: "button", "aria-label": `Trang ${item}`, "aria-current": item === page ? 'page' : undefined, onClick: () => onPageChange(item), className: cn(pageButtonClass, item === page && 'bg-brand-secondary font-semibold text-white'), children: item }, item))), _jsx(LarkButton, { isIconOnly: true, variant: "ghost", size: "sm", "aria-label": "Trang sau", disabled: page >= totalPages, onClick: () => onPageChange(page + 1), children: _jsx(ChevronRight, { className: "size-4", strokeWidth: 2.4 }) })] }), _jsx(LarkSelect, { ariaLabel: "S\u1ED1 d\u00F2ng m\u1ED7i trang", options: pageSizes.map((s) => ({ value: String(s), label: String(s) })), value: String(pageSize), onValueChange: (v) => onPageSizeChange?.(Number(v)), className: "w-20" })] }));
26
+ return (_jsxs("div", { ref: ref, className: cn('flex flex-wrap items-center justify-end gap-4', className), ...props, children: [isLoading ? _jsx(LarkSpinner, { size: "sm", label: "\u0110ang t\u1EA3i d\u1EEF li\u1EC7u" }) : null, _jsxs("nav", { "aria-label": "Ph\u00E2n trang", className: "flex items-center gap-1", children: [_jsx(LarkButton, { isIconOnly: true, variant: "ghost", size: "sm", "aria-label": "Trang tr\u01B0\u1EDBc", disabled: page <= 1, onClick: () => onPageChange(page - 1), children: _jsx(ChevronLeft, { className: "size-4", strokeWidth: 2.4 }) }), items.map((item, index) => item === 'ellipsis' ? (_jsx("span", { "aria-hidden": "true", className: "px-1 text-lark-n400", children: "\u2026" }, `gap-${index}`)) : (_jsx("button", { type: "button", "aria-label": `Trang ${item}`, "aria-current": item === page ? 'page' : undefined, onClick: () => onPageChange(item), className: cn(pageButtonClass, item === page && 'bg-brand-secondary font-semibold text-white'), children: item }, item))), _jsx(LarkButton, { isIconOnly: true, variant: "ghost", size: "sm", "aria-label": "Trang sau", disabled: page >= totalPages, onClick: () => onPageChange(page + 1), children: _jsx(ChevronRight, { className: "size-4", strokeWidth: 2.4 }) })] }), _jsx(LarkSelect, { ariaLabel: "S\u1ED1 d\u00F2ng m\u1ED7i trang", options: pageSizes.map((s) => ({ value: String(s), label: String(s) })), value: String(pageSize), onValueChange: (v) => onPageSizeChange?.(Number(v)), className: "w-20" })] }));
27
27
  }
28
28
  const LarkPaginationInner = React.forwardRef(LarkPaginationBase);
29
29
  LarkPaginationInner.displayName = 'LarkPagination';
@@ -1,22 +1,26 @@
1
1
  import * as React from 'react';
2
+ import * as RadioGroup from '@radix-ui/react-radio-group';
2
3
  export interface LarkRadioOption {
3
4
  value: string;
4
5
  label: string;
5
6
  disabled?: boolean;
6
7
  }
7
- export interface LarkRadioGroupProps {
8
+ export interface LarkRadioGroupProps extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroup.Root>, 'value' | 'onValueChange' | 'className' | 'children'> {
8
9
  label?: string;
9
- options: LarkRadioOption[];
10
+ options?: LarkRadioOption[];
10
11
  value?: string;
11
12
  onValueChange?: (value: string) => void;
12
13
  className?: string;
14
+ /** Thay cho `options` khi cần tự bố trí item (dùng `LarkRadioGroup.Item`). */
15
+ children?: React.ReactNode;
13
16
  }
14
- export interface LarkRadioItemProps {
17
+ export interface LarkRadioItemProps extends Omit<React.ComponentPropsWithoutRef<typeof RadioGroup.Item>, 'value' | 'disabled' | 'className' | 'id'> {
15
18
  value: string;
16
19
  label: string;
17
20
  disabled?: boolean;
18
21
  id?: string;
19
22
  className?: string;
23
+ controlClassName?: string;
20
24
  }
21
25
  declare const LarkRadioItemInner: React.ForwardRefExoticComponent<LarkRadioItemProps & React.RefAttributes<HTMLButtonElement>>;
22
26
  declare const LarkRadioGroupRoot: React.ForwardRefExoticComponent<LarkRadioGroupProps & React.RefAttributes<HTMLDivElement>>;
@@ -4,17 +4,19 @@ import * as React from 'react';
4
4
  import * as Label from '@radix-ui/react-label';
5
5
  import * as RadioGroup from '@radix-ui/react-radio-group';
6
6
  import { cn } from '../lib/utils.js';
7
- function LarkRadioItemBase({ value, label, disabled, id, className }, ref) {
7
+ function LarkRadioItemBase({ value, label, disabled, id, className, controlClassName, ...props }, ref) {
8
8
  const autoId = React.useId();
9
9
  const itemId = id ?? `${autoId}-${value}`;
10
- return (_jsxs("span", { className: cn('flex items-center gap-2', className), children: [_jsx(RadioGroup.Item, { ref: ref, id: itemId, value: value, disabled: disabled, className: "size-4 shrink-0 rounded-full border border-lark-n500 bg-brand-primary focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:border-[5px] data-[state=checked]:border-brand-secondary disabled:cursor-not-allowed disabled:opacity-50" }), _jsx(Label.Root, { htmlFor: itemId, className: "text-sm text-lark-n900", children: label })] }));
10
+ return (_jsxs("span", { className: cn('flex items-center gap-2', className), children: [_jsx(RadioGroup.Item, { ref: ref, id: itemId, value: value, disabled: disabled, className: cn('size-4 shrink-0 rounded-full border border-lark-n500 bg-brand-primary focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:border-[5px] data-[state=checked]:border-brand-secondary disabled:cursor-not-allowed disabled:opacity-50', controlClassName), ...props }), _jsx(Label.Root, { htmlFor: itemId, className: "text-sm text-lark-n900", children: label })] }));
11
11
  }
12
12
  const LarkRadioItemInner = React.forwardRef(LarkRadioItemBase);
13
13
  LarkRadioItemInner.displayName = 'LarkRadioGroup.Item';
14
- function LarkRadioGroupBase({ label, options, value, onValueChange, className }, ref) {
14
+ function LarkRadioGroupBase({ label, options, value, onValueChange, className, id, children, ...props }, ref) {
15
15
  const autoId = React.useId();
16
16
  const labelId = label ? `${autoId}-label` : undefined;
17
- return (_jsxs("div", { className: cn('flex flex-col', className), children: [label ? (_jsx("span", { id: labelId, className: "mb-1 text-sm text-lark-n900", children: label })) : null, _jsx(RadioGroup.Root, { ref: ref, value: value, onValueChange: onValueChange, "aria-labelledby": labelId, className: "flex flex-col gap-2", children: options.map((option) => (_jsx(LarkRadioItemInner, { value: option.value, label: option.label, disabled: option.disabled, id: `${autoId}-${option.value}` }, option.value))) })] }));
17
+ return (_jsxs("div", { className: cn('flex flex-col', className), children: [label ? (_jsx("span", { id: labelId, className: "mb-1 text-sm text-lark-n900", children: label })) : null, _jsx(RadioGroup.Root, { ref: ref, id: id, value: value, onValueChange: onValueChange, "aria-labelledby": labelId, className: "flex flex-col gap-2", ...props, children: options
18
+ ? options.map((option) => (_jsx(LarkRadioItemInner, { value: option.value, label: option.label, disabled: option.disabled, id: `${autoId}-${option.value}` }, option.value)))
19
+ : children })] }));
18
20
  }
19
21
  const LarkRadioGroupRoot = React.forwardRef(LarkRadioGroupBase);
20
22
  LarkRadioGroupRoot.displayName = 'LarkRadioGroup';
@@ -1,19 +1,23 @@
1
1
  import * as React from 'react';
2
+ import * as Select from '@radix-ui/react-select';
2
3
  export interface LarkSelectOption {
3
4
  value: string;
4
5
  label: string;
5
6
  disabled?: boolean;
6
7
  }
7
- export interface LarkSelectProps {
8
+ export interface LarkSelectProps extends Omit<React.ComponentPropsWithoutRef<typeof Select.Trigger>, 'value' | 'className' | 'disabled'> {
8
9
  label?: string;
9
10
  options: LarkSelectOption[];
10
11
  value?: string;
11
12
  onValueChange?: (v: string) => void;
13
+ onOpenChange?: (open: boolean) => void;
12
14
  placeholder?: string;
13
15
  disabled?: boolean;
14
16
  isInvalid?: boolean;
15
17
  className?: string;
16
18
  ariaLabel?: string;
19
+ /** Class cho wrapper ngoài (label + trigger). */
20
+ containerClassName?: string;
17
21
  }
18
22
  declare const LarkSelectInner: React.ForwardRefExoticComponent<LarkSelectProps & React.RefAttributes<HTMLButtonElement>>;
19
23
  export { LarkSelectInner as LarkSelect };
@@ -4,9 +4,10 @@ import * as React from 'react';
4
4
  import * as Select from '@radix-ui/react-select';
5
5
  import { Check, ChevronDown } from 'lucide-react';
6
6
  import { cn } from '../lib/utils.js';
7
- function LarkSelectBase({ label, options, value, onValueChange, placeholder, disabled, isInvalid, className, ariaLabel }, ref) {
7
+ function LarkSelectBase({ label, options, value, onValueChange, onOpenChange, placeholder, disabled, isInvalid, className, ariaLabel, containerClassName, id, ...props }, ref) {
8
8
  const autoId = React.useId();
9
- return (_jsxs("div", { className: "flex w-full flex-col gap-1.5", children: [label ? (_jsx("label", { htmlFor: autoId, className: "text-sm text-lark-n700", children: label })) : null, _jsxs(Select.Root, { value: value, onValueChange: onValueChange, disabled: disabled, children: [_jsxs(Select.Trigger, { ref: ref, id: autoId, "aria-label": ariaLabel, "aria-invalid": isInvalid || undefined, className: cn('flex h-8 min-h-8 w-full items-center justify-between gap-2 rounded-lg border border-lark-n300 bg-brand-primary px-3 text-sm focus-visible:focus-ring transition-colors duration-fast ease-base hover:border-brand-secondary focus-visible:border-[var(--field-border-focus)] disabled:cursor-not-allowed disabled:bg-lark-n50 disabled:text-lark-n400', isInvalid && 'border-lark-danger', className), children: [_jsx(Select.Value, { placeholder: placeholder, className: "data-[placeholder]:text-lark-n600" }), _jsx(Select.Icon, { asChild: true, children: _jsx(ChevronDown, { className: "size-4 text-lark-n500" }) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { position: "popper", className: "max-h-[min(20rem,60vh)] min-w-[var(--radix-select-trigger-width)] overflow-y-auto rounded-lg border border-lark-n200 bg-brand-primary p-1 shadow-[var(--shadow-overlay)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out", children: _jsx(Select.Viewport, { children: options.map((opt) => (_jsxs(Select.Item, { value: opt.value, disabled: opt.disabled, className: "flex h-8 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 text-sm focus-visible:focus-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-lark-n100 data-[state=checked]:font-medium data-[state=checked]:text-brand-link", children: [_jsx(Select.ItemText, { children: opt.label }), _jsx(Select.ItemIndicator, { children: _jsx(Check, { className: "size-4" }) })] }, opt.value))) }) }) })] })] }));
9
+ const selectId = id ?? autoId;
10
+ return (_jsxs("div", { className: cn('flex w-full flex-col gap-1.5', containerClassName), children: [label ? (_jsx("label", { htmlFor: selectId, className: "text-sm text-lark-n700", children: label })) : null, _jsxs(Select.Root, { value: value, onValueChange: onValueChange, onOpenChange: onOpenChange, disabled: disabled, children: [_jsxs(Select.Trigger, { ref: ref, id: selectId, "aria-label": ariaLabel, "aria-invalid": isInvalid || undefined, className: cn('flex h-8 min-h-8 w-full items-center justify-between gap-2 rounded-lg border border-lark-n300 bg-brand-primary px-3 text-sm focus-visible:focus-ring transition-colors duration-fast ease-base hover:border-brand-secondary focus-visible:border-[var(--field-border-focus)] disabled:cursor-not-allowed disabled:bg-lark-n50 disabled:text-lark-n400', isInvalid && 'border-lark-danger', className), ...props, children: [_jsx(Select.Value, { placeholder: placeholder, className: "data-[placeholder]:text-lark-n600" }), _jsx(Select.Icon, { asChild: true, children: _jsx(ChevronDown, { className: "size-4 text-lark-n500" }) })] }), _jsx(Select.Portal, { children: _jsx(Select.Content, { position: "popper", className: "max-h-[min(20rem,60vh)] min-w-[var(--radix-select-trigger-width)] overflow-y-auto rounded-lg border border-lark-n200 bg-brand-primary p-1 shadow-[var(--shadow-overlay)] data-[state=open]:anim-overlay-in data-[state=closed]:anim-overlay-out", children: _jsx(Select.Viewport, { children: options.map((opt) => (_jsxs(Select.Item, { value: opt.value, disabled: opt.disabled, className: "flex h-8 cursor-pointer items-center justify-between gap-2 rounded-lg px-3 text-sm focus-visible:focus-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[highlighted]:bg-lark-n100 data-[state=checked]:font-medium data-[state=checked]:text-brand-link", children: [_jsx(Select.ItemText, { children: opt.label }), _jsx(Select.ItemIndicator, { children: _jsx(Check, { className: "size-4" }) })] }, opt.value))) }) }) })] })] }));
10
11
  }
11
12
  const LarkSelectInner = React.forwardRef(LarkSelectBase);
12
13
  LarkSelectInner.displayName = 'LarkSelect';
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- export interface LarkSpinnerProps {
2
+ import { Loader2 } from 'lucide-react';
3
+ export interface LarkSpinnerProps extends Omit<React.ComponentPropsWithoutRef<typeof Loader2>, 'className'> {
3
4
  size?: 'sm' | 'md';
4
5
  label?: string;
5
6
  className?: string;
6
7
  }
7
- export declare function LarkSpinner({ size, label, className }: LarkSpinnerProps): React.JSX.Element;
8
+ export declare function LarkSpinner({ size, label, className, ...props }: LarkSpinnerProps): React.JSX.Element;
@@ -6,9 +6,9 @@ const spinnerSizes = {
6
6
  md: 'size-6',
7
7
  };
8
8
  const IS_DEV = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production';
9
- export function LarkSpinner({ size = 'md', label, className }) {
9
+ export function LarkSpinner({ size = 'md', label, className, ...props }) {
10
10
  if (IS_DEV && !label) {
11
11
  console.warn('LarkSpinner: aria-label ("label" prop) bắt buộc');
12
12
  }
13
- return (_jsx(Loader2, { role: "status", "aria-label": label, className: cn('animate-spin text-lark-n500', spinnerSizes[size], className) }));
13
+ return (_jsx(Loader2, { role: "status", "aria-label": label, className: cn('animate-spin text-lark-n500', spinnerSizes[size], className), ...props }));
14
14
  }
@@ -1,10 +1,12 @@
1
1
  import * as React from 'react';
2
- export interface LarkSwitchProps {
2
+ import * as Switch from '@radix-ui/react-switch';
3
+ export interface LarkSwitchProps extends Omit<React.ComponentPropsWithoutRef<typeof Switch.Root>, 'checked' | 'onCheckedChange' | 'disabled' | 'className'> {
3
4
  label?: string;
4
5
  checked?: boolean;
5
6
  onCheckedChange?: (checked: boolean) => void;
6
7
  disabled?: boolean;
7
8
  className?: string;
9
+ controlClassName?: string;
8
10
  }
9
11
  declare const LarkSwitchInner: React.ForwardRefExoticComponent<LarkSwitchProps & React.RefAttributes<HTMLButtonElement>>;
10
12
  export { LarkSwitchInner as LarkSwitch };
@@ -4,12 +4,13 @@ import * as React from 'react';
4
4
  import * as Label from '@radix-ui/react-label';
5
5
  import * as Switch from '@radix-ui/react-switch';
6
6
  import { cn } from '../lib/utils.js';
7
- function LarkSwitchBase({ label, checked, onCheckedChange, disabled, className }, ref) {
7
+ function LarkSwitchBase({ label, checked, onCheckedChange, disabled, className, controlClassName, id, ...props }, ref) {
8
8
  const autoId = React.useId();
9
- const trigger = (_jsx(Switch.Root, { ref: ref, id: autoId, checked: checked, onCheckedChange: onCheckedChange, disabled: disabled, className: "h-5 w-9 shrink-0 rounded-full bg-lark-n300 focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:bg-brand-secondary disabled:cursor-not-allowed disabled:opacity-50", children: _jsx(Switch.Thumb, { className: "block size-4 rounded-full bg-white shadow-sm transition-transform duration-fast ease-base data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5" }) }));
9
+ const switchId = id ?? autoId;
10
+ const trigger = (_jsx(Switch.Root, { ref: ref, id: switchId, checked: checked, onCheckedChange: onCheckedChange, disabled: disabled, className: cn('h-5 w-9 shrink-0 rounded-full bg-lark-n300 focus-visible:focus-ring transition-colors duration-fast ease-base data-[state=checked]:bg-brand-secondary disabled:cursor-not-allowed disabled:opacity-50', controlClassName), ...props, children: _jsx(Switch.Thumb, { className: "block size-4 rounded-full bg-white shadow-sm transition-transform duration-fast ease-base data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5" }) }));
10
11
  if (!label)
11
12
  return _jsx("div", { className: className, children: trigger });
12
- return (_jsxs("div", { className: cn('flex items-center gap-2', className), children: [trigger, _jsx(Label.Root, { htmlFor: autoId, className: "text-sm text-lark-n900", children: label })] }));
13
+ return (_jsxs("div", { className: cn('flex items-center gap-2', className), children: [trigger, _jsx(Label.Root, { htmlFor: switchId, className: "text-sm text-lark-n900", children: label })] }));
13
14
  }
14
15
  const LarkSwitchInner = React.forwardRef(LarkSwitchBase);
15
16
  LarkSwitchInner.displayName = 'LarkSwitch';
@@ -6,31 +6,33 @@ export interface LarkColumn<T> {
6
6
  align?: 'left' | 'right' | 'center';
7
7
  cell?: (row: T) => React.ReactNode;
8
8
  pin?: 'right';
9
+ /** Class áp cho cả `th` và `td` của cột này. */
10
+ className?: string;
11
+ /** Class riêng cho `th` (thắng `className`). */
12
+ headerClassName?: string;
9
13
  }
10
- export interface LarkTableProps<T> {
14
+ export interface LarkTableProps<T> extends Omit<React.TableHTMLAttributes<HTMLTableElement>, 'children' | 'className' | 'rowKey'> {
11
15
  columns: LarkColumn<T>[];
12
16
  rows: T[];
13
17
  rowKey: (row: T) => string;
14
18
  isLoading?: boolean;
15
19
  emptyState?: React.ReactNode;
16
20
  className?: string;
21
+ /** React 19: ref là prop thường nên gắn được vào <table>. */
22
+ ref?: React.Ref<HTMLTableElement>;
23
+ onRowClick?: (row: T, index: number) => void;
24
+ rowClassName?: (row: T, index: number) => string | undefined;
17
25
  }
18
- export interface LarkTableWrapperProps {
19
- children: React.ReactNode;
20
- className?: string;
26
+ export interface LarkTableWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
21
27
  }
22
- export declare function LarkTableWrapper({ children, className }: LarkTableWrapperProps): React.JSX.Element;
23
- export interface LarkTableFooterProps {
24
- children: React.ReactNode;
25
- className?: string;
28
+ export declare function LarkTableWrapper({ children, className, ...props }: LarkTableWrapperProps): React.JSX.Element;
29
+ export interface LarkTableFooterProps extends React.HTMLAttributes<HTMLDivElement> {
26
30
  }
27
- export declare function LarkTableFooter({ children, className }: LarkTableFooterProps): React.JSX.Element;
28
- export interface LarkTruncatedCellProps {
29
- children: React.ReactNode;
30
- className?: string;
31
+ export declare function LarkTableFooter({ children, className, ...props }: LarkTableFooterProps): React.JSX.Element;
32
+ export interface LarkTruncatedCellProps extends React.HTMLAttributes<HTMLDivElement> {
31
33
  title?: string;
32
34
  }
33
- export declare function LarkTruncatedCell({ children, className, title }: LarkTruncatedCellProps): React.JSX.Element;
35
+ export declare function LarkTruncatedCell({ children, className, title, ...props }: LarkTruncatedCellProps): React.JSX.Element;
34
36
  type LarkTableComponent = (<T>(props: LarkTableProps<T>) => React.JSX.Element) & {
35
37
  Wrapper: typeof LarkTableWrapper;
36
38
  Footer: typeof LarkTableFooter;
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../lib/utils.js';
3
- export function LarkTableWrapper({ children, className }) {
4
- return _jsx("div", { className: cn('min-h-0 flex-1 overflow-x-auto', className), children: children });
3
+ export function LarkTableWrapper({ children, className, ...props }) {
4
+ return (_jsx("div", { className: cn('min-h-0 flex-1 overflow-x-auto', className), ...props, children: children }));
5
5
  }
6
- export function LarkTableFooter({ children, className }) {
7
- return (_jsx("div", { className: cn('flex w-full flex-wrap items-center justify-between gap-4 border-t border-lark-n200 bg-brand-primary px-3 py-2 text-sm text-lark-n600 sm:justify-end', className), children: children }));
6
+ export function LarkTableFooter({ children, className, ...props }) {
7
+ return (_jsx("div", { className: cn('flex w-full flex-wrap items-center justify-between gap-4 border-t border-lark-n200 bg-brand-primary px-3 py-2 text-sm text-lark-n600 sm:justify-end', className), ...props, children: children }));
8
8
  }
9
9
  const alignClass = {
10
10
  left: 'text-left',
@@ -17,14 +17,14 @@ function defaultCell(row, key) {
17
17
  return null;
18
18
  return value;
19
19
  }
20
- function LarkTableInner({ columns, rows, rowKey, isLoading, emptyState, className }) {
20
+ function LarkTableInner({ columns, rows, rowKey, isLoading, emptyState, className, ref, onRowClick, rowClassName, ...props }) {
21
21
  const showEmpty = !isLoading && rows.length === 0;
22
- return (_jsx("div", { className: "min-h-0 flex-1 overflow-x-auto", children: _jsxs("table", { className: cn('w-full min-w-max border-collapse', className), "aria-busy": isLoading ? 'true' : undefined, children: [isLoading && _jsx("caption", { className: "sr-only", children: "\u0110ang t\u1EA3i" }), _jsx("thead", { children: _jsx("tr", { className: "border-b border-lark-n300", children: columns.map((col) => (_jsx("th", { scope: "col", style: col.width ? { width: col.width } : undefined, className: cn('sticky top-0 z-10 h-12 bg-brand-primary px-3 align-middle text-sm font-medium text-lark-n600', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-20 min-w-px whitespace-nowrap border-s border-lark-n200'), children: col.header }, col.key))) }) }), _jsxs("tbody", { children: [isLoading &&
23
- [0, 1, 2].map((index) => (_jsx("tr", { className: "animate-pulse border-b border-lark-n200 bg-lark-n50", children: columns.map((col) => (_jsx("td", { className: cn('h-[47px] px-3 align-middle text-sm text-lark-n900', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-10 min-w-px bg-brand-primary whitespace-nowrap border-s border-lark-n200'), children: _jsx("div", { className: "h-3 w-2/3 rounded bg-lark-n200" }) }, col.key))) }, `skeleton-${index}`))), showEmpty && (_jsx("tr", { className: "border-b border-lark-n200", children: _jsx("td", { colSpan: columns.length, className: "px-3 py-8 text-center text-sm text-lark-n600", children: emptyState ?? 'Không có dữ liệu' }) })), !isLoading &&
22
+ return (_jsx("div", { className: "min-h-0 flex-1 overflow-x-auto", children: _jsxs("table", { ref: ref, className: cn('w-full min-w-max border-collapse', className), "aria-busy": isLoading ? 'true' : undefined, ...props, children: [isLoading && _jsx("caption", { className: "sr-only", children: "\u0110ang t\u1EA3i" }), _jsx("thead", { children: _jsx("tr", { className: "border-b border-lark-n300", children: columns.map((col) => (_jsx("th", { scope: "col", style: col.width ? { width: col.width } : undefined, className: cn('sticky top-0 z-10 h-12 bg-brand-primary px-3 align-middle text-sm font-medium text-lark-n600', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-20 min-w-px whitespace-nowrap border-s border-lark-n200', col.className, col.headerClassName), children: col.header }, col.key))) }) }), _jsxs("tbody", { children: [isLoading &&
23
+ [0, 1, 2].map((index) => (_jsx("tr", { className: "animate-pulse border-b border-lark-n200 bg-lark-n50", children: columns.map((col) => (_jsx("td", { className: cn('h-[47px] px-3 align-middle text-sm text-lark-n900', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-10 min-w-px bg-brand-primary whitespace-nowrap border-s border-lark-n200', col.className), children: _jsx("div", { className: "h-3 w-2/3 rounded bg-lark-n200" }) }, col.key))) }, `skeleton-${index}`))), showEmpty && (_jsx("tr", { className: "border-b border-lark-n200", children: _jsx("td", { colSpan: columns.length, className: "px-3 py-8 text-center text-sm text-lark-n600", children: emptyState ?? 'Không có dữ liệu' }) })), !isLoading &&
24
24
  !showEmpty &&
25
- rows.map((row) => (_jsx("tr", { className: "border-b border-lark-n200", children: columns.map((col) => (_jsx("td", { className: cn('h-[47px] px-3 align-middle text-sm text-lark-n900', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-10 min-w-px bg-brand-primary whitespace-nowrap border-s border-lark-n200'), children: col.cell ? col.cell(row) : defaultCell(row, col.key) }, col.key))) }, rowKey(row))))] })] }) }));
25
+ rows.map((row, rowIndex) => (_jsx("tr", { className: cn('border-b border-lark-n200', rowClassName?.(row, rowIndex)), onClick: onRowClick ? () => onRowClick(row, rowIndex) : undefined, children: columns.map((col) => (_jsx("td", { className: cn('h-[47px] px-3 align-middle text-sm text-lark-n900', alignClass[col.align ?? 'left'], col.pin === 'right' && 'sticky right-0 z-10 min-w-px bg-brand-primary whitespace-nowrap border-s border-lark-n200', col.className), children: col.cell ? col.cell(row) : defaultCell(row, col.key) }, col.key))) }, rowKey(row))))] })] }) }));
26
26
  }
27
- export function LarkTruncatedCell({ children, className, title }) {
28
- return (_jsx("div", { title: title, className: cn('line-clamp-2 max-w-64 cursor-pointer whitespace-normal text-start', className), children: children }));
27
+ export function LarkTruncatedCell({ children, className, title, ...props }) {
28
+ return (_jsx("div", { title: title, className: cn('line-clamp-2 max-w-64 cursor-pointer whitespace-normal text-start', className), ...props, children: children }));
29
29
  }
30
30
  export const LarkTable = Object.assign(LarkTableInner, { Wrapper: LarkTableWrapper, Footer: LarkTableFooter });
@@ -1,29 +1,25 @@
1
1
  import * as React from 'react';
2
- export interface LarkTabsProps {
3
- defaultValue?: string;
4
- value?: string;
5
- onValueChange?: (value: string) => void;
6
- children: React.ReactNode;
7
- className?: string;
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
3
+ export interface LarkTabsProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {
8
4
  }
9
- export interface LarkTabsListProps {
10
- 'aria-label'?: string;
11
- children: React.ReactNode;
5
+ export interface LarkTabsListProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>, 'className'> {
12
6
  className?: string;
13
7
  }
14
- export interface LarkTabProps {
8
+ export interface LarkTabProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>, 'value' | 'className'> {
15
9
  value: string;
16
- children: React.ReactNode;
17
- disabled?: boolean;
18
10
  className?: string;
19
11
  }
20
- export interface LarkPanelProps {
12
+ export interface LarkPanelProps extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>, 'value' | 'className'> {
21
13
  value: string;
22
- children: React.ReactNode;
23
14
  className?: string;
24
15
  }
25
- export declare const LarkTabs: React.FC<LarkTabsProps> & {
26
- List: React.FC<LarkTabsListProps>;
27
- Tab: React.FC<LarkTabProps>;
28
- Panel: React.FC<LarkPanelProps>;
16
+ declare const LarkTabsRoot: React.ForwardRefExoticComponent<LarkTabsProps & React.RefAttributes<HTMLDivElement>>;
17
+ declare const LarkTabsList: React.ForwardRefExoticComponent<LarkTabsListProps & React.RefAttributes<HTMLDivElement>>;
18
+ declare const LarkTab: React.ForwardRefExoticComponent<LarkTabProps & React.RefAttributes<HTMLButtonElement>>;
19
+ declare const LarkPanel: React.ForwardRefExoticComponent<LarkPanelProps & React.RefAttributes<HTMLDivElement>>;
20
+ export declare const LarkTabs: typeof LarkTabsRoot & {
21
+ List: typeof LarkTabsList;
22
+ Tab: typeof LarkTab;
23
+ Panel: typeof LarkPanel;
29
24
  };
25
+ export {};