@vetc-miniapp/ui-react 0.0.23 → 0.0.25

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 (146) hide show
  1. package/README.md +375 -56
  2. package/dist/bridge.d.ts +11 -0
  3. package/dist/bridge.js +20 -0
  4. package/dist/components/app.d.ts +6 -0
  5. package/dist/components/app.js +34 -0
  6. package/dist/components/avatar/Avatar.d.ts +21 -0
  7. package/dist/components/avatar/Avatar.js +33 -0
  8. package/dist/components/avatar/index.d.ts +2 -0
  9. package/dist/components/avatar/index.js +1 -0
  10. package/dist/components/bottom-sheet/BottomSheet.d.ts +19 -0
  11. package/dist/components/bottom-sheet/BottomSheet.js +70 -0
  12. package/dist/components/bottom-sheet/index.d.ts +2 -0
  13. package/dist/components/bottom-sheet/index.js +1 -0
  14. package/dist/components/button/Button.d.ts +32 -0
  15. package/dist/components/button/Button.js +165 -0
  16. package/dist/components/button/index.d.ts +2 -0
  17. package/dist/components/button/index.js +1 -0
  18. package/dist/components/button-group/ButtonGroup.d.ts +28 -0
  19. package/dist/components/button-group/ButtonGroup.js +21 -0
  20. package/dist/components/button-group/index.d.ts +2 -0
  21. package/dist/components/button-group/index.js +1 -0
  22. package/dist/components/card/Card.d.ts +18 -0
  23. package/dist/components/card/Card.js +35 -0
  24. package/dist/components/card/index.d.ts +2 -0
  25. package/dist/components/card/index.js +1 -0
  26. package/dist/components/checkbox/Checkbox.d.ts +41 -0
  27. package/dist/components/checkbox/Checkbox.js +94 -0
  28. package/dist/components/checkbox/index.d.ts +2 -0
  29. package/dist/components/checkbox/index.js +1 -0
  30. package/dist/components/chip/Chip.d.ts +24 -0
  31. package/dist/components/chip/Chip.js +83 -0
  32. package/dist/components/chip/index.d.ts +2 -0
  33. package/dist/components/chip/index.js +1 -0
  34. package/dist/components/dialog/Dialog.d.ts +19 -0
  35. package/dist/components/dialog/Dialog.js +51 -0
  36. package/dist/components/dialog/index.d.ts +2 -0
  37. package/dist/components/dialog/index.js +1 -0
  38. package/dist/components/divider/Divider.d.ts +16 -0
  39. package/dist/components/divider/Divider.js +18 -0
  40. package/dist/components/divider/index.d.ts +2 -0
  41. package/dist/components/divider/index.js +1 -0
  42. package/dist/components/input/Input.d.ts +40 -0
  43. package/dist/components/input/Input.js +51 -0
  44. package/dist/components/input/index.d.ts +2 -0
  45. package/dist/components/input/index.js +1 -0
  46. package/dist/components/list/List.d.ts +31 -0
  47. package/dist/components/list/List.js +72 -0
  48. package/dist/components/list/index.d.ts +2 -0
  49. package/dist/components/list/index.js +1 -0
  50. package/dist/components/loading/Loading.d.ts +28 -0
  51. package/dist/components/loading/Loading.js +33 -0
  52. package/dist/components/loading/index.d.ts +2 -0
  53. package/dist/components/loading/index.js +1 -0
  54. package/dist/components/modal/Modal.d.ts +38 -0
  55. package/dist/components/modal/Modal.js +50 -0
  56. package/dist/components/modal/index.d.ts +2 -0
  57. package/dist/components/modal/index.js +1 -0
  58. package/dist/components/navigation-bar/NavigationBar.d.ts +44 -0
  59. package/dist/components/navigation-bar/NavigationBar.js +70 -0
  60. package/dist/components/navigation-bar/index.d.ts +2 -0
  61. package/dist/components/navigation-bar/index.js +1 -0
  62. package/dist/components/radio/Radio.d.ts +40 -0
  63. package/dist/components/radio/Radio.js +88 -0
  64. package/dist/components/radio/index.d.ts +2 -0
  65. package/dist/components/radio/index.js +1 -0
  66. package/dist/components/select/Select.d.ts +29 -0
  67. package/dist/components/select/Select.js +30 -0
  68. package/dist/components/select/index.d.ts +2 -0
  69. package/dist/components/select/index.js +1 -0
  70. package/dist/components/switch/Switch.d.ts +23 -0
  71. package/dist/components/switch/Switch.js +81 -0
  72. package/dist/components/switch/index.d.ts +2 -0
  73. package/dist/components/switch/index.js +1 -0
  74. package/dist/components/tab-bar/TabBar.d.ts +28 -0
  75. package/dist/components/tab-bar/TabBar.js +60 -0
  76. package/dist/components/tab-bar/index.d.ts +2 -0
  77. package/dist/components/tab-bar/index.js +1 -0
  78. package/dist/components/textarea/Textarea.d.ts +31 -0
  79. package/dist/components/textarea/Textarea.js +33 -0
  80. package/dist/components/textarea/index.d.ts +2 -0
  81. package/dist/components/textarea/index.js +1 -0
  82. package/dist/components/toast/Toast.d.ts +41 -0
  83. package/dist/components/toast/Toast.js +61 -0
  84. package/dist/components/toast/index.d.ts +2 -0
  85. package/dist/components/toast/index.js +1 -0
  86. package/dist/components/typography/Typography.d.ts +45 -0
  87. package/dist/components/typography/Typography.js +143 -0
  88. package/dist/components/typography/index.d.ts +2 -0
  89. package/dist/components/typography/index.js +1 -0
  90. package/dist/hooks/use-app-pause.d.ts +6 -0
  91. package/dist/hooks/use-app-pause.js +29 -0
  92. package/dist/hooks/use-app-resume.d.ts +6 -0
  93. package/dist/hooks/use-app-resume.js +28 -0
  94. package/{src/ui-react/hooks/use-app-state.ts → dist/hooks/use-app-state.d.ts} +0 -1
  95. package/dist/hooks/use-app-state.js +1 -0
  96. package/dist/hooks/use-did-hide.d.ts +6 -0
  97. package/dist/hooks/use-did-hide.js +21 -0
  98. package/dist/hooks/use-did-show.d.ts +6 -0
  99. package/dist/hooks/use-did-show.js +21 -0
  100. package/dist/hooks/use-listener-scan-qr.d.ts +21 -0
  101. package/dist/hooks/use-listener-scan-qr.js +29 -0
  102. package/dist/hooks/use-navigate.d.ts +8 -0
  103. package/dist/hooks/use-navigate.js +23 -0
  104. package/dist/hooks/use-tap-app-bar.d.ts +6 -0
  105. package/dist/hooks/use-tap-app-bar.js +21 -0
  106. package/dist/index.d.ts +56 -0
  107. package/dist/index.js +41 -0
  108. package/dist/styles/VETCProvider.d.ts +114 -0
  109. package/dist/styles/VETCProvider.js +124 -0
  110. package/dist/styles/tokens.css +448 -0
  111. package/dist/tokens/colors.d.ts +127 -0
  112. package/dist/tokens/colors.js +75 -0
  113. package/dist/tokens/index.d.ts +3 -0
  114. package/dist/tokens/index.js +3 -0
  115. package/dist/tokens/spacing.d.ts +56 -0
  116. package/dist/tokens/spacing.js +56 -0
  117. package/dist/tokens/typography.d.ts +121 -0
  118. package/dist/tokens/typography.js +57 -0
  119. package/dist/types/app.d.ts +21 -0
  120. package/dist/types/app.js +1 -0
  121. package/package.json +26 -8
  122. package/src/dist/ui-react/index.js +0 -2
  123. package/src/dist/ui-react/index.js.LICENSE.txt +0 -11
  124. package/src/ui-react/bridge.js +0 -36
  125. package/src/ui-react/bridge.ts +0 -48
  126. package/src/ui-react/components/app.d.ts +0 -7
  127. package/src/ui-react/components/app.jsx +0 -80
  128. package/src/ui-react/components/app.tsx +0 -42
  129. package/src/ui-react/components/app1.js +0 -101
  130. package/src/ui-react/hooks/use-app-pause.js +0 -35
  131. package/src/ui-react/hooks/use-app-pause.ts +0 -33
  132. package/src/ui-react/hooks/use-app-resume.js +0 -37
  133. package/src/ui-react/hooks/use-app-resume.ts +0 -32
  134. package/src/ui-react/hooks/use-app-state.js +0 -35
  135. package/src/ui-react/hooks/use-did-hide.js +0 -25
  136. package/src/ui-react/hooks/use-did-hide.ts +0 -34
  137. package/src/ui-react/hooks/use-did-show.js +0 -26
  138. package/src/ui-react/hooks/use-did-show.ts +0 -34
  139. package/src/ui-react/hooks/use-listener-scan-qr.js +0 -33
  140. package/src/ui-react/hooks/use-listener-scan-qr.ts +0 -52
  141. package/src/ui-react/hooks/use-navigate.js +0 -15
  142. package/src/ui-react/hooks/use-navigate.ts +0 -41
  143. package/src/ui-react/index.js +0 -8
  144. package/src/ui-react/index.ts +0 -9
  145. package/src/ui-react/types/app.js +0 -30
  146. package/src/ui-react/types/app.ts +0 -32
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Tag as AntTag } from 'antd';
3
+ // Map type → CSS vars (from tokens.css --vetc-chip-*)
4
+ const typeVarMap = {
5
+ default: {
6
+ bg: 'var(--vetc-chip-default-bg)',
7
+ border: 'var(--vetc-chip-default-border)',
8
+ text: 'var(--vetc-chip-default-text)',
9
+ filledBg: 'var(--vetc-chip-default-filled-bg)',
10
+ },
11
+ brand: {
12
+ bg: 'var(--vetc-chip-brand-bg)',
13
+ border: 'var(--vetc-chip-brand-border)',
14
+ text: 'var(--vetc-chip-brand-text)',
15
+ filledBg: 'var(--vetc-chip-brand-filled-bg)',
16
+ },
17
+ positive: {
18
+ bg: 'var(--vetc-chip-positive-bg)',
19
+ border: 'var(--vetc-chip-positive-border)',
20
+ text: 'var(--vetc-chip-positive-text)',
21
+ filledBg: 'var(--vetc-chip-positive-filled-bg)',
22
+ },
23
+ negative: {
24
+ bg: 'var(--vetc-chip-negative-bg)',
25
+ border: 'var(--vetc-chip-negative-border)',
26
+ text: 'var(--vetc-chip-negative-text)',
27
+ filledBg: 'var(--vetc-chip-negative-filled-bg)',
28
+ },
29
+ warning: {
30
+ bg: 'var(--vetc-chip-warning-bg)',
31
+ border: 'var(--vetc-chip-warning-border)',
32
+ text: 'var(--vetc-chip-warning-text)',
33
+ filledBg: 'var(--vetc-chip-warning-filled-bg)',
34
+ },
35
+ info: {
36
+ bg: 'var(--vetc-chip-info-bg)',
37
+ border: 'var(--vetc-chip-info-border)',
38
+ text: 'var(--vetc-chip-info-text)',
39
+ filledBg: 'var(--vetc-chip-info-filled-bg)',
40
+ },
41
+ };
42
+ export function Chip({ style = 'tinted', type = 'default', shape = 'pill', size = 'md', icon, closable = false, onClose, onClick, children, className = '', css, id, }) {
43
+ const vars = typeVarMap[type];
44
+ const height = size === 'md' ? 'var(--vetc-chip-height-md)' : 'var(--vetc-chip-height-sm)';
45
+ const paddingX = size === 'md' ? 'var(--vetc-chip-padding-x-md)' : 'var(--vetc-chip-padding-x-sm)';
46
+ const fontSize = size === 'md' ? 'var(--vetc-chip-font-size-md)' : 'var(--vetc-chip-font-size-sm)';
47
+ const fontWeight = 'var(--vetc-chip-font-weight)';
48
+ const borderRadius = shape === 'pill' ? 'var(--vetc-chip-radius-pill)' : 'var(--vetc-chip-radius-rounded)';
49
+ let bg = vars.bg;
50
+ let border = `1px solid ${vars.border}`;
51
+ let textColor = vars.text;
52
+ if (style === 'filled') {
53
+ bg = vars.filledBg;
54
+ border = 'none';
55
+ textColor = 'var(--vetc-white)';
56
+ }
57
+ else if (style === 'outlined') {
58
+ bg = 'transparent';
59
+ border = `1px solid ${vars.border}`;
60
+ textColor = vars.text;
61
+ }
62
+ // tinted: bg + border (default)
63
+ return (_jsx(AntTag, { id: id, closable: closable, onClose: (e) => { e.preventDefault(); onClose?.(); }, onClick: onClick, icon: icon, className: `vetc-chip vetc-chip--${style} vetc-chip--${type} ${className}`, style: {
64
+ display: 'inline-flex',
65
+ alignItems: 'center',
66
+ height,
67
+ padding: `0 ${paddingX}`,
68
+ borderRadius,
69
+ background: bg,
70
+ border,
71
+ color: textColor,
72
+ fontSize,
73
+ fontWeight: fontWeight,
74
+ fontFamily: 'var(--vetc-font-family)',
75
+ cursor: onClick ? 'pointer' : 'default',
76
+ userSelect: 'none',
77
+ lineHeight: 1,
78
+ gap: 'var(--vetc-space-4)',
79
+ transition: 'var(--vetc-transition-fast)',
80
+ ...css,
81
+ }, children: children }));
82
+ }
83
+ export default Chip;
@@ -0,0 +1,2 @@
1
+ export { Chip } from './Chip';
2
+ export type { ChipProps, ChipStyle, ChipType, ChipShape } from './Chip';
@@ -0,0 +1 @@
1
+ export { Chip } from './Chip';
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ export interface DialogProps {
3
+ open: boolean;
4
+ /** Optional image URL shown at the top of the dialog */
5
+ image?: string;
6
+ title?: React.ReactNode;
7
+ description?: React.ReactNode;
8
+ okText?: string;
9
+ cancelText?: string;
10
+ okDisabled?: boolean;
11
+ okLoading?: boolean;
12
+ okDanger?: boolean;
13
+ onOk?: () => void;
14
+ onCancel?: () => void;
15
+ /** Close dialog when clicking outside (default: true) */
16
+ maskClosable?: boolean;
17
+ }
18
+ export declare function Dialog({ open, image, title, description, okText, cancelText, okDisabled, okLoading, okDanger, onOk, onCancel, maskClosable, }: DialogProps): React.ReactPortal;
19
+ export default Dialog;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Button } from '../button';
5
+ export function Dialog({ open, image, title, description, okText = 'Xác nhận', cancelText = 'Hủy', okDisabled = false, okLoading = false, okDanger = false, onOk, onCancel, maskClosable = true, }) {
6
+ useEffect(() => {
7
+ if (open)
8
+ document.body.style.overflow = 'hidden';
9
+ else
10
+ document.body.style.overflow = '';
11
+ return () => { document.body.style.overflow = ''; };
12
+ }, [open]);
13
+ if (!open)
14
+ return null;
15
+ const hasContent = title || description;
16
+ return createPortal(_jsx("div", { style: {
17
+ position: 'fixed',
18
+ inset: 0,
19
+ backgroundColor: 'var(--vetc-dialog-overlay)',
20
+ display: 'flex',
21
+ alignItems: 'center',
22
+ justifyContent: 'center',
23
+ zIndex: 1000,
24
+ padding: '0 var(--vetc-space-24)',
25
+ }, onClick: maskClosable ? onCancel : undefined, children: _jsxs("div", { style: {
26
+ width: '100%',
27
+ maxWidth: 'var(--vetc-dialog-width)',
28
+ backgroundColor: 'var(--vetc-dialog-bg)',
29
+ borderRadius: 'var(--vetc-dialog-radius)',
30
+ overflow: 'hidden',
31
+ boxShadow: 'var(--vetc-shadow-lg)',
32
+ fontFamily: 'var(--vetc-font-family)',
33
+ }, onClick: e => e.stopPropagation(), children: [image && (_jsx("div", { style: { width: '100%', height: 'var(--vetc-dialog-image-height)', overflow: 'hidden', flexShrink: 0 }, children: _jsx("img", { src: image, alt: "", style: { width: '100%', height: '100%', objectFit: 'cover', display: 'block' } }) })), hasContent && (_jsxs("div", { style: { padding: 'var(--vetc-dialog-padding)' }, children: [title && (_jsx("div", { style: {
34
+ fontSize: 'var(--vetc-dialog-title-size)',
35
+ fontWeight: 'var(--vetc-dialog-title-weight)',
36
+ color: 'var(--vetc-color-text-primary)',
37
+ lineHeight: 'var(--vetc-line-height-relaxed)',
38
+ marginBottom: description ? 'var(--vetc-space-8)' : 0,
39
+ }, children: title })), description && (_jsx("div", { style: {
40
+ fontSize: 'var(--vetc-dialog-desc-size)',
41
+ color: 'var(--vetc-dialog-desc-color)',
42
+ lineHeight: 'var(--vetc-line-height-relaxed)',
43
+ }, children: description }))] })), _jsxs("div", { style: {
44
+ display: 'flex',
45
+ gap: 'var(--vetc-dialog-btn-gap)',
46
+ padding: hasContent
47
+ ? `0 var(--vetc-dialog-padding) var(--vetc-dialog-padding)`
48
+ : 'var(--vetc-dialog-padding)',
49
+ }, children: [onCancel && (_jsx(Button, { block: true, style: "outlined", variant: "neutral", onClick: onCancel, children: cancelText })), _jsx(Button, { block: true, style: okDanger ? 'danger' : 'filled', loading: okLoading, disabled: okDisabled, onClick: onOk, children: okText })] })] }) }), document.body);
50
+ }
51
+ export default Dialog;
@@ -0,0 +1,2 @@
1
+ export { Dialog } from './Dialog';
2
+ export type { DialogProps } from './Dialog';
@@ -0,0 +1 @@
1
+ export { Dialog } from './Dialog';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * VETC Divider — tokenized
3
+ */
4
+ import React from 'react';
5
+ export interface DividerProps {
6
+ orientation?: 'horizontal' | 'vertical';
7
+ label?: React.ReactNode;
8
+ labelAlign?: 'left' | 'center' | 'right';
9
+ thickness?: number;
10
+ color?: string;
11
+ margin?: string;
12
+ className?: string;
13
+ style?: React.CSSProperties;
14
+ }
15
+ export declare function Divider({ orientation, label, labelAlign, thickness, color, margin, className, style, }: DividerProps): import("react/jsx-runtime").JSX.Element;
16
+ export default Divider;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Divider as AntDivider } from 'antd';
3
+ export function Divider({ orientation = 'horizontal', label, labelAlign = 'center', thickness = 1, color, margin, className = '', style, }) {
4
+ const isVertical = orientation === 'vertical';
5
+ return (_jsx(AntDivider, { type: isVertical ? 'vertical' : 'horizontal', orientationMargin: labelAlign !== 'center' ? 0 : undefined, orientation: labelAlign !== 'center' ? labelAlign : undefined, className: `vetc-divider ${className}`, style: {
6
+ borderColor: color ?? 'var(--vetc-divider-color)',
7
+ borderTopWidth: !isVertical ? thickness : undefined,
8
+ borderLeftWidth: isVertical ? thickness : undefined,
9
+ margin: margin ?? (isVertical
10
+ ? `0 var(--vetc-divider-margin-v)`
11
+ : `var(--vetc-divider-margin-h) 0`),
12
+ fontFamily: 'var(--vetc-font-family)',
13
+ fontSize: 'var(--vetc-divider-label-size)',
14
+ color: 'var(--vetc-divider-label-color)',
15
+ ...style,
16
+ }, children: label }));
17
+ }
18
+ export default Divider;
@@ -0,0 +1,2 @@
1
+ export { Divider } from './Divider';
2
+ export type { DividerProps } from './Divider';
@@ -0,0 +1 @@
1
+ export { Divider } from './Divider';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * VETC Input / TextField & PasswordInput Components
3
+ * All style values use CSS custom properties from tokens.css
4
+ * Figma: Text field page — height 48px, radius 8px, padding-x 12px
5
+ */
6
+ import React from 'react';
7
+ export type InputStatus = 'default' | 'error' | 'success';
8
+ export interface InputProps {
9
+ label?: string;
10
+ placeholder?: string;
11
+ value?: string;
12
+ defaultValue?: string;
13
+ helperText?: string;
14
+ /** Status affecting border color */
15
+ status?: InputStatus;
16
+ /** Error message — sets status=error automatically */
17
+ error?: string;
18
+ disabled?: boolean;
19
+ readOnly?: boolean;
20
+ maxLength?: number;
21
+ showCount?: boolean;
22
+ prefix?: React.ReactNode;
23
+ suffix?: React.ReactNode;
24
+ allowClear?: boolean;
25
+ onChange?: (value: string, event: React.ChangeEvent<HTMLInputElement>) => void;
26
+ onFocus?: React.FocusEventHandler<HTMLInputElement>;
27
+ onBlur?: React.FocusEventHandler<HTMLInputElement>;
28
+ onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;
29
+ type?: string;
30
+ id?: string;
31
+ name?: string;
32
+ className?: string;
33
+ style?: React.CSSProperties;
34
+ required?: boolean;
35
+ }
36
+ export declare function Input({ label, placeholder, value, defaultValue, helperText, status, error, disabled, readOnly, maxLength, showCount, prefix, suffix, allowClear, onChange, onFocus, onBlur, onPressEnter, type, id, name, className, style, required, }: InputProps): import("react/jsx-runtime").JSX.Element;
37
+ export interface PasswordInputProps extends Omit<InputProps, 'type' | 'suffix' | 'allowClear'> {
38
+ }
39
+ export declare function PasswordInput({ label, placeholder, value, defaultValue, helperText, status, error, disabled, readOnly, maxLength, onChange, onFocus, onBlur, id, name, className, style, required, }: PasswordInputProps): import("react/jsx-runtime").JSX.Element;
40
+ export default Input;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Input as AntInput } from 'antd';
3
+ // ── Shared label/helper sub-components ───────────────────────────────────────
4
+ function FieldLabel({ htmlFor, required, disabled, children }) {
5
+ return (_jsxs("label", { htmlFor: htmlFor, style: {
6
+ fontFamily: 'var(--vetc-font-family)',
7
+ fontSize: 'var(--vetc-input-label-font-size)',
8
+ fontWeight: 'var(--vetc-input-label-font-weight)',
9
+ lineHeight: 'var(--vetc-line-height-relaxed)',
10
+ color: disabled
11
+ ? 'var(--vetc-color-text-disabled)'
12
+ : 'var(--vetc-input-label-color)',
13
+ display: 'block',
14
+ }, children: [children, required && (_jsx("span", { style: { color: 'var(--vetc-color-negative)', marginLeft: 'var(--vetc-space-2)' }, children: "*" }))] }));
15
+ }
16
+ function HelperText({ color, children }) {
17
+ return (_jsx("span", { style: {
18
+ fontFamily: 'var(--vetc-font-family)',
19
+ fontSize: 'var(--vetc-input-helper-font-size)',
20
+ lineHeight: 'var(--vetc-line-height-relaxed)',
21
+ color,
22
+ }, children: children }));
23
+ }
24
+ const inputFieldStyle = {
25
+ height: 'var(--vetc-input-height)',
26
+ borderRadius: 'var(--vetc-input-radius)',
27
+ paddingInline: 'var(--vetc-input-padding-x)',
28
+ fontSize: 'var(--vetc-input-font-size)',
29
+ fontFamily: 'var(--vetc-font-family)',
30
+ };
31
+ export function Input({ label, placeholder, value, defaultValue, helperText, status = 'default', error, disabled = false, readOnly = false, maxLength, showCount = false, prefix, suffix, allowClear = false, onChange, onFocus, onBlur, onPressEnter, type = 'text', id, name, className = '', style, required = false, }) {
32
+ const hasError = !!error;
33
+ const resolvedStatus = hasError ? 'error' : status;
34
+ const antStatus = resolvedStatus === 'error' ? 'error' : undefined;
35
+ const helperMsg = error ?? helperText;
36
+ const helperColor = resolvedStatus === 'error'
37
+ ? 'var(--vetc-input-helper-color-error)'
38
+ : 'var(--vetc-input-helper-color)';
39
+ return (_jsxs("div", { className: `vetc-input-wrapper ${className}`, style: { display: 'flex', flexDirection: 'column', gap: 'var(--vetc-input-gap)', ...style }, children: [label && _jsx(FieldLabel, { htmlFor: id, required: required, disabled: disabled, children: label }), _jsx(AntInput, { id: id, name: name, type: type, value: value, defaultValue: defaultValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, maxLength: maxLength, showCount: showCount, prefix: prefix, suffix: suffix, allowClear: allowClear, status: antStatus, onChange: (e) => onChange?.(e.target.value, e), onFocus: onFocus, onBlur: onBlur, onPressEnter: onPressEnter, style: inputFieldStyle }), helperMsg && _jsx(HelperText, { color: helperColor, children: helperMsg })] }));
40
+ }
41
+ export function PasswordInput({ label, placeholder, value, defaultValue, helperText, status = 'default', error, disabled = false, readOnly = false, maxLength, onChange, onFocus, onBlur, id, name, className = '', style, required = false, }) {
42
+ const hasError = !!error;
43
+ const resolvedStatus = hasError ? 'error' : status;
44
+ const antStatus = resolvedStatus === 'error' ? 'error' : undefined;
45
+ const helperMsg = error ?? helperText;
46
+ const helperColor = hasError
47
+ ? 'var(--vetc-input-helper-color-error)'
48
+ : 'var(--vetc-input-helper-color)';
49
+ return (_jsxs("div", { className: `vetc-input-wrapper ${className ?? ''}`, style: { display: 'flex', flexDirection: 'column', gap: 'var(--vetc-input-gap)', ...style }, children: [label && _jsx(FieldLabel, { htmlFor: id, required: required, disabled: disabled, children: label }), _jsx(AntInput.Password, { id: id, name: name, value: value, defaultValue: defaultValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, maxLength: maxLength, status: antStatus, onChange: (e) => onChange?.(e.target.value, e), onFocus: onFocus, onBlur: onBlur, style: inputFieldStyle }), helperMsg && _jsx(HelperText, { color: helperColor, children: helperMsg })] }));
50
+ }
51
+ export default Input;
@@ -0,0 +1,2 @@
1
+ export { Input, PasswordInput } from './Input';
2
+ export type { InputProps, PasswordInputProps, InputStatus } from './Input';
@@ -0,0 +1 @@
1
+ export { Input, PasswordInput } from './Input';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * VETC List & ListItem — tokenized
3
+ * Figma: List item page — height 56px, padding-x 16px
4
+ */
5
+ import React from 'react';
6
+ export interface ListItemProps {
7
+ leading?: React.ReactNode;
8
+ title: React.ReactNode;
9
+ description?: React.ReactNode;
10
+ trailing?: React.ReactNode;
11
+ /** Show right chevron */
12
+ arrow?: boolean;
13
+ /** Bottom divider line */
14
+ divider?: boolean;
15
+ onClick?: () => void;
16
+ disabled?: boolean;
17
+ className?: string;
18
+ style?: React.CSSProperties;
19
+ id?: string;
20
+ }
21
+ export declare function ListItem({ leading, title, description, trailing, arrow, divider, onClick, disabled, className, style, id, }: ListItemProps): import("react/jsx-runtime").JSX.Element;
22
+ export interface ListProps<T = any> {
23
+ items: T[];
24
+ renderItem: (item: T, index: number) => React.ReactNode;
25
+ bordered?: boolean;
26
+ className?: string;
27
+ style?: React.CSSProperties;
28
+ id?: string;
29
+ }
30
+ export declare function List<T = any>({ items, renderItem, bordered, className, style, id, }: ListProps<T>): import("react/jsx-runtime").JSX.Element;
31
+ export default ListItem;
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * VETC List & ListItem — tokenized
4
+ * Figma: List item page — height 56px, padding-x 16px
5
+ */
6
+ import React from 'react';
7
+ // ── ChevronRight Icon ─────────────────────────────────────────────────────────
8
+ function ChevronRightIcon({ disabled }) {
9
+ return (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M7.5 5L12.5 10L7.5 15", stroke: disabled ? 'var(--vetc-color-icon-disabled)' : 'var(--vetc-color-icon-muted)', strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
10
+ }
11
+ export function ListItem({ leading, title, description, trailing, arrow = false, divider = true, onClick, disabled = false, className = '', style, id, }) {
12
+ const isClickable = !!onClick && !disabled;
13
+ return (_jsxs("div", { id: id, role: isClickable ? 'button' : undefined, tabIndex: isClickable ? 0 : undefined, className: `vetc-list-item ${className}`, onClick: !disabled ? onClick : undefined, onKeyDown: isClickable ? (e) => { if (e.key === 'Enter' || e.key === ' ')
14
+ onClick?.(); } : undefined, style: {
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ minHeight: 'var(--vetc-list-item-height)',
18
+ padding: `0 var(--vetc-list-item-padding-x)`,
19
+ gap: 'var(--vetc-list-item-gap)',
20
+ cursor: isClickable ? 'pointer' : 'default',
21
+ borderBottom: divider ? `1px solid var(--vetc-list-item-border)` : 'none',
22
+ backgroundColor: 'var(--vetc-list-item-bg)',
23
+ opacity: disabled ? 0.5 : 1,
24
+ fontFamily: 'var(--vetc-font-family)',
25
+ transition: `background-color var(--vetc-transition-fast)`,
26
+ outline: 'none',
27
+ ...style,
28
+ }, onMouseEnter: (e) => {
29
+ if (isClickable)
30
+ e.currentTarget.style.backgroundColor = 'var(--vetc-list-item-bg-hover)';
31
+ }, onMouseLeave: (e) => {
32
+ if (isClickable)
33
+ e.currentTarget.style.backgroundColor = 'var(--vetc-list-item-bg)';
34
+ }, children: [leading && (_jsx("div", { style: {
35
+ flexShrink: 0,
36
+ display: 'flex',
37
+ alignItems: 'center',
38
+ color: 'var(--vetc-list-item-icon-color)',
39
+ }, children: leading })), _jsxs("div", { style: { flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 'var(--vetc-space-2)' }, children: [_jsx("span", { style: {
40
+ fontSize: 'var(--vetc-list-item-title-size)',
41
+ fontWeight: 'var(--vetc-list-item-title-weight)',
42
+ color: disabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-list-item-title-color)',
43
+ lineHeight: 'var(--vetc-line-height-relaxed)',
44
+ overflow: 'hidden',
45
+ textOverflow: 'ellipsis',
46
+ whiteSpace: 'nowrap',
47
+ }, children: title }), description && (_jsx("span", { style: {
48
+ fontSize: 'var(--vetc-list-item-desc-size)',
49
+ fontWeight: 'var(--vetc-font-weight-regular)',
50
+ color: disabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-list-item-desc-color)',
51
+ lineHeight: 'var(--vetc-line-height-relaxed)',
52
+ overflow: 'hidden',
53
+ textOverflow: 'ellipsis',
54
+ whiteSpace: 'nowrap',
55
+ }, children: description }))] }), (trailing || arrow) && (_jsxs("div", { style: {
56
+ flexShrink: 0,
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: 'var(--vetc-space-8)',
60
+ color: 'var(--vetc-list-item-icon-color)',
61
+ }, children: [trailing, arrow && _jsx(ChevronRightIcon, { disabled: disabled })] }))] }));
62
+ }
63
+ export function List({ items, renderItem, bordered = false, className = '', style, id, }) {
64
+ return (_jsx("div", { id: id, className: `vetc-list ${className}`, style: {
65
+ border: bordered ? `1px solid var(--vetc-color-border-variant)` : 'none',
66
+ borderRadius: bordered ? 'var(--vetc-card-radius)' : 0,
67
+ overflow: 'hidden',
68
+ backgroundColor: 'var(--vetc-color-bg)',
69
+ ...style,
70
+ }, children: items.map((item, index) => (_jsx(React.Fragment, { children: renderItem(item, index) }, index))) }));
71
+ }
72
+ export default ListItem;
@@ -0,0 +1,2 @@
1
+ export { List, ListItem } from './List';
2
+ export type { ListProps, ListItemProps } from './List';
@@ -0,0 +1 @@
1
+ export { List, ListItem } from './List';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * VETC Loading — tokenized
3
+ */
4
+ import React from 'react';
5
+ export type SpinnerSize = 'sm' | 'md' | 'lg';
6
+ export interface SpinnerProps {
7
+ size?: SpinnerSize;
8
+ color?: string;
9
+ fullscreen?: boolean;
10
+ tip?: string;
11
+ children?: React.ReactNode;
12
+ spinning?: boolean;
13
+ className?: string;
14
+ style?: React.CSSProperties;
15
+ }
16
+ export declare function Spinner({ size, color, fullscreen, tip, children, spinning, className, style, }: SpinnerProps): import("react/jsx-runtime").JSX.Element;
17
+ export interface SkeletonProps {
18
+ rows?: number;
19
+ avatar?: boolean;
20
+ avatarSize?: number;
21
+ title?: boolean;
22
+ loading?: boolean;
23
+ className?: string;
24
+ style?: React.CSSProperties;
25
+ children?: React.ReactNode;
26
+ }
27
+ export declare function SkeletonLoader({ rows, avatar, avatarSize, title, loading, className, style, children, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
28
+ export default Spinner;
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Spin, Skeleton } from 'antd';
3
+ import { LoadingOutlined } from '@ant-design/icons';
4
+ const spinnerSizeVarMap = {
5
+ sm: 'var(--vetc-spinner-size-sm)',
6
+ md: 'var(--vetc-spinner-size-md)',
7
+ lg: 'var(--vetc-spinner-size-lg)',
8
+ };
9
+ // antd icon needs numeric fontSize — CSS var won't work directly
10
+ const spinnerPxMap = { sm: 16, md: 24, lg: 40 };
11
+ export function Spinner({ size = 'md', color, fullscreen = false, tip, children, spinning = true, className = '', style, }) {
12
+ const icon = (_jsx(LoadingOutlined, { style: { fontSize: spinnerPxMap[size], color: color ?? 'var(--vetc-spinner-color)' }, spin: true }));
13
+ if (children) {
14
+ return (_jsx(Spin, { spinning: spinning, indicator: icon, tip: tip, className: `vetc-spinner ${className}`, style: style, children: children }));
15
+ }
16
+ if (fullscreen) {
17
+ return (_jsx("div", { className: `vetc-spinner-overlay ${className}`, style: {
18
+ position: 'fixed',
19
+ inset: 0,
20
+ display: 'flex',
21
+ alignItems: 'center',
22
+ justifyContent: 'center',
23
+ backgroundColor: 'var(--vetc-color-overlay)',
24
+ zIndex: 9999,
25
+ ...style,
26
+ }, children: _jsx(Spin, { spinning: spinning, indicator: icon, tip: tip }) }));
27
+ }
28
+ return (_jsx("div", { className: `vetc-spinner ${className}`, style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', ...style }, children: _jsx(Spin, { spinning: spinning, indicator: icon, tip: tip }) }));
29
+ }
30
+ export function SkeletonLoader({ rows = 3, avatar = false, avatarSize = 40, title = true, loading = true, className = '', style, children, }) {
31
+ return (_jsx(Skeleton, { active: true, loading: loading, avatar: avatar ? { size: avatarSize, shape: 'circle' } : false, title: title, paragraph: { rows }, className: `vetc-skeleton ${className}`, style: style, children: children }));
32
+ }
33
+ export default Spinner;
@@ -0,0 +1,2 @@
1
+ export { Spinner, SkeletonLoader } from './Loading';
2
+ export type { SpinnerProps, SkeletonProps, SpinnerSize } from './Loading';
@@ -0,0 +1 @@
1
+ export { Spinner, SkeletonLoader } from './Loading';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * VETC Modal / Dialog — tokenized
3
+ * Figma: Dialog page — width 344px, radius 16px, overlay rgba(0,0,0,0.4)
4
+ */
5
+ import React from 'react';
6
+ export interface ModalProps {
7
+ open: boolean;
8
+ title?: React.ReactNode;
9
+ children?: React.ReactNode;
10
+ /** null = hide footer, undefined = default buttons, ReactNode = custom */
11
+ footer?: React.ReactNode | null;
12
+ okText?: string;
13
+ cancelText?: string;
14
+ okDisabled?: boolean;
15
+ okLoading?: boolean;
16
+ okDanger?: boolean;
17
+ onOk?: () => void;
18
+ onCancel?: () => void;
19
+ maskClosable?: boolean;
20
+ closable?: boolean;
21
+ width?: number | string;
22
+ className?: string;
23
+ style?: React.CSSProperties;
24
+ id?: string;
25
+ }
26
+ export declare function Modal({ open, title, children, footer, okText, cancelText, okDisabled, okLoading, okDanger, onOk, onCancel, maskClosable, closable, width, className, style, id, }: ModalProps): import("react/jsx-runtime").JSX.Element;
27
+ export declare function useConfirm(): {
28
+ confirm: (opts: {
29
+ title: string;
30
+ content?: React.ReactNode;
31
+ okText?: string;
32
+ cancelText?: string;
33
+ okDanger?: boolean;
34
+ onOk?: () => void | Promise<void>;
35
+ onCancel?: () => void;
36
+ }) => void;
37
+ };
38
+ export default Modal;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Modal as AntModal } from 'antd';
3
+ /** Shared button style for modal actions */
4
+ const modalBtnStyle = {
5
+ height: 'var(--vetc-btn-height-lg)',
6
+ borderRadius: 'var(--vetc-btn-radius-lg)',
7
+ fontWeight: 'var(--vetc-btn-font-weight)',
8
+ fontSize: 'var(--vetc-btn-font-size-lg)',
9
+ fontFamily: 'var(--vetc-font-family)',
10
+ };
11
+ export function Modal({ open, title, children, footer, okText = 'Xác nhận', cancelText = 'Hủy', okDisabled = false, okLoading = false, okDanger = false, onOk, onCancel, maskClosable = true, closable = true, width, className = '', style, id, }) {
12
+ return (_jsx(AntModal, { open: open, title: title, okText: okText, cancelText: cancelText, okButtonProps: {
13
+ disabled: okDisabled,
14
+ loading: okLoading,
15
+ danger: okDanger,
16
+ style: modalBtnStyle,
17
+ }, cancelButtonProps: { style: modalBtnStyle }, footer: footer === null ? null : footer, onOk: onOk, onCancel: onCancel, maskClosable: maskClosable, closable: closable, width: width ?? 'var(--vetc-modal-width)', centered: true, className: `vetc-modal ${className}`, style: { fontFamily: 'var(--vetc-font-family)', ...style }, styles: {
18
+ header: {
19
+ padding: `var(--vetc-modal-padding) var(--vetc-modal-padding) 0`,
20
+ borderBottom: 'none',
21
+ },
22
+ body: {
23
+ padding: `var(--vetc-space-8) var(--vetc-modal-padding) var(--vetc-modal-padding)`,
24
+ },
25
+ footer: {
26
+ padding: `0 var(--vetc-modal-padding) var(--vetc-modal-padding)`,
27
+ borderTop: 'none',
28
+ },
29
+ mask: { backgroundColor: 'var(--vetc-modal-overlay)' },
30
+ }, children: children }));
31
+ }
32
+ // ── Confirm hook ──────────────────────────────────────────────────────────────
33
+ export function useConfirm() {
34
+ const confirm = (opts) => {
35
+ AntModal.confirm({
36
+ title: opts.title,
37
+ content: opts.content,
38
+ okText: opts.okText ?? 'Xác nhận',
39
+ cancelText: opts.cancelText ?? 'Hủy',
40
+ okButtonProps: { danger: opts.okDanger, style: { ...modalBtnStyle, height: 40 } },
41
+ cancelButtonProps: { style: { ...modalBtnStyle, height: 40 } },
42
+ centered: true,
43
+ onOk: opts.onOk,
44
+ onCancel: opts.onCancel,
45
+ styles: { mask: { backgroundColor: 'var(--vetc-modal-overlay)' } },
46
+ });
47
+ };
48
+ return { confirm };
49
+ }
50
+ export default Modal;
@@ -0,0 +1,2 @@
1
+ export { Modal, useConfirm } from './Modal';
2
+ export type { ModalProps } from './Modal';
@@ -0,0 +1 @@
1
+ export { Modal, useConfirm } from './Modal';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * VETC NavigationBar — Top navigation bar
3
+ * Figma: h=56px, title=20px/semibold, icon-btn=32px, padding-x=8px
4
+ * Variants: back button, close button, action buttons (max 2 right)
5
+ */
6
+ import React from 'react';
7
+ export interface NavigationBarAction {
8
+ icon: React.ReactNode;
9
+ label?: string;
10
+ onClick?: () => void;
11
+ disabled?: boolean;
12
+ id?: string;
13
+ }
14
+ export type NavigationBarLeadingType = 'back' | 'close' | 'none';
15
+ export interface NavigationBarProps {
16
+ title?: React.ReactNode;
17
+ /** Leading left button type */
18
+ leading?: NavigationBarLeadingType;
19
+ /** Custom back/close icon (overrides default) */
20
+ leadingIcon?: React.ReactNode;
21
+ onLeadingPress?: () => void;
22
+ /** Max 2 action buttons on right */
23
+ actions?: NavigationBarAction[];
24
+ /** Fully custom left slot (overrides leading) */
25
+ leftSlot?: React.ReactNode;
26
+ /** Fully custom right slot (overrides actions) */
27
+ rightSlot?: React.ReactNode;
28
+ backgroundColor?: string;
29
+ divider?: boolean;
30
+ transparent?: boolean;
31
+ className?: string;
32
+ style?: React.CSSProperties;
33
+ id?: string;
34
+ /** @deprecated use leading="back" + onLeadingPress */
35
+ showBack?: boolean;
36
+ /** @deprecated use leadingIcon */
37
+ backIcon?: React.ReactNode;
38
+ /** @deprecated use onLeadingPress */
39
+ onBack?: () => void;
40
+ left?: React.ReactNode;
41
+ right?: React.ReactNode;
42
+ }
43
+ export declare function NavigationBar({ title, leading, leadingIcon, onLeadingPress, actions, leftSlot, rightSlot, backgroundColor, divider, transparent, className, style, id, showBack, backIcon, onBack, left, right, }: NavigationBarProps): import("react/jsx-runtime").JSX.Element;
44
+ export default NavigationBar;