@vetc-miniapp/ui-react 0.0.24 → 0.0.26

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 (172) hide show
  1. package/dist/bridge.d.ts +17 -0
  2. package/dist/bridge.js +20 -0
  3. package/dist/components/app.d.ts +17 -0
  4. package/dist/components/app.js +34 -0
  5. package/dist/components/avatar/Avatar.d.ts +21 -0
  6. package/dist/components/avatar/Avatar.js +33 -0
  7. package/dist/components/avatar/index.js +1 -0
  8. package/dist/components/bottom-sheet/BottomSheet.d.ts +19 -0
  9. package/dist/components/bottom-sheet/BottomSheet.js +70 -0
  10. package/dist/components/bottom-sheet/index.js +1 -0
  11. package/dist/components/button/Button.d.ts +32 -0
  12. package/dist/components/button/Button.js +165 -0
  13. package/dist/components/button/index.js +1 -0
  14. package/dist/components/button-group/ButtonGroup.d.ts +28 -0
  15. package/dist/components/button-group/ButtonGroup.js +21 -0
  16. package/dist/components/button-group/index.js +1 -0
  17. package/dist/components/card/Card.d.ts +18 -0
  18. package/dist/components/card/Card.js +35 -0
  19. package/dist/components/card/index.js +1 -0
  20. package/dist/components/checkbox/Checkbox.d.ts +41 -0
  21. package/dist/components/checkbox/Checkbox.js +94 -0
  22. package/dist/components/checkbox/index.js +1 -0
  23. package/dist/components/chip/Chip.d.ts +24 -0
  24. package/dist/components/chip/Chip.js +83 -0
  25. package/dist/components/chip/index.js +1 -0
  26. package/dist/components/dialog/Dialog.d.ts +19 -0
  27. package/dist/components/dialog/Dialog.js +51 -0
  28. package/dist/components/dialog/index.js +1 -0
  29. package/dist/components/divider/Divider.d.ts +16 -0
  30. package/dist/components/divider/Divider.js +18 -0
  31. package/dist/components/divider/index.js +1 -0
  32. package/dist/components/input/Input.d.ts +40 -0
  33. package/dist/components/input/Input.js +51 -0
  34. package/dist/components/input/index.js +1 -0
  35. package/dist/components/list/List.d.ts +31 -0
  36. package/dist/components/list/List.js +72 -0
  37. package/dist/components/list/index.js +1 -0
  38. package/dist/components/loading/Loading.d.ts +28 -0
  39. package/dist/components/loading/Loading.js +33 -0
  40. package/dist/components/loading/index.js +1 -0
  41. package/dist/components/modal/Modal.d.ts +38 -0
  42. package/dist/components/modal/Modal.js +50 -0
  43. package/dist/components/modal/index.js +1 -0
  44. package/dist/components/navigation-bar/NavigationBar.d.ts +44 -0
  45. package/dist/components/navigation-bar/NavigationBar.js +70 -0
  46. package/dist/components/navigation-bar/index.js +1 -0
  47. package/dist/components/radio/Radio.d.ts +40 -0
  48. package/dist/components/radio/Radio.js +88 -0
  49. package/dist/components/radio/index.js +1 -0
  50. package/dist/components/select/Select.d.ts +29 -0
  51. package/dist/components/select/Select.js +30 -0
  52. package/dist/components/select/index.js +1 -0
  53. package/dist/components/switch/Switch.d.ts +23 -0
  54. package/dist/components/switch/Switch.js +81 -0
  55. package/dist/components/switch/index.js +1 -0
  56. package/dist/components/tab-bar/TabBar.d.ts +28 -0
  57. package/dist/components/tab-bar/TabBar.js +60 -0
  58. package/dist/components/tab-bar/index.js +1 -0
  59. package/dist/components/textarea/Textarea.d.ts +31 -0
  60. package/dist/components/textarea/Textarea.js +33 -0
  61. package/dist/components/textarea/index.js +1 -0
  62. package/dist/components/toast/Toast.d.ts +41 -0
  63. package/dist/components/toast/Toast.js +61 -0
  64. package/dist/components/toast/index.js +1 -0
  65. package/dist/components/typography/Typography.d.ts +45 -0
  66. package/dist/components/typography/Typography.js +143 -0
  67. package/dist/components/typography/index.js +1 -0
  68. package/dist/hooks/use-app-pause.d.ts +4 -0
  69. package/dist/hooks/use-app-pause.js +25 -0
  70. package/dist/hooks/use-app-resume.d.ts +4 -0
  71. package/dist/hooks/use-app-resume.js +25 -0
  72. package/{src/ui-react/hooks/use-app-state.ts → dist/hooks/use-app-state.d.ts} +0 -1
  73. package/dist/hooks/use-app-state.js +1 -0
  74. package/dist/hooks/use-did-hide.d.ts +6 -0
  75. package/dist/hooks/use-did-hide.js +20 -0
  76. package/dist/hooks/use-did-show.d.ts +6 -0
  77. package/dist/hooks/use-did-show.js +20 -0
  78. package/dist/hooks/use-listener-scan-qr.d.ts +10 -0
  79. package/dist/hooks/use-listener-scan-qr.js +25 -0
  80. package/dist/hooks/use-navigate.d.ts +8 -0
  81. package/dist/hooks/use-navigate.js +23 -0
  82. package/dist/hooks/use-tap-app-bar.d.ts +6 -0
  83. package/dist/hooks/use-tap-app-bar.js +20 -0
  84. package/{src/ui-react/index.ts → dist/index.d.ts} +1 -30
  85. package/dist/index.js +41 -0
  86. package/dist/styles/VETCProvider.d.ts +114 -0
  87. package/dist/styles/VETCProvider.js +124 -0
  88. package/{src/ui-react → dist}/styles/tokens.css +22 -1
  89. package/dist/tokens/colors.d.ts +127 -0
  90. package/dist/tokens/colors.js +75 -0
  91. package/dist/tokens/index.js +3 -0
  92. package/dist/tokens/spacing.d.ts +56 -0
  93. package/dist/tokens/spacing.js +56 -0
  94. package/dist/tokens/typography.d.ts +121 -0
  95. package/dist/tokens/typography.js +57 -0
  96. package/dist/types/app.d.ts +21 -0
  97. package/dist/types/app.js +1 -0
  98. package/package.json +13 -7
  99. package/src/dist/ui-react/index.js +0 -2
  100. package/src/dist/ui-react/index.js.LICENSE.txt +0 -11
  101. package/src/ui-react/bridge.js +0 -36
  102. package/src/ui-react/bridge.ts +0 -48
  103. package/src/ui-react/components/app.d.ts +0 -7
  104. package/src/ui-react/components/app.jsx +0 -80
  105. package/src/ui-react/components/app.tsx +0 -42
  106. package/src/ui-react/components/app1.js +0 -101
  107. package/src/ui-react/components/avatar/Avatar.tsx +0 -88
  108. package/src/ui-react/components/bottom-sheet/BottomSheet.tsx +0 -149
  109. package/src/ui-react/components/button/Button.tsx +0 -246
  110. package/src/ui-react/components/button-group/ButtonGroup.tsx +0 -108
  111. package/src/ui-react/components/card/Card.tsx +0 -77
  112. package/src/ui-react/components/checkbox/Checkbox.tsx +0 -232
  113. package/src/ui-react/components/chip/Chip.tsx +0 -137
  114. package/src/ui-react/components/dialog/Dialog.tsx +0 -135
  115. package/src/ui-react/components/divider/Divider.tsx +0 -54
  116. package/src/ui-react/components/input/Input.tsx +0 -195
  117. package/src/ui-react/components/list/List.tsx +0 -180
  118. package/src/ui-react/components/loading/Loading.tsx +0 -121
  119. package/src/ui-react/components/modal/Modal.tsx +0 -116
  120. package/src/ui-react/components/navigation-bar/NavigationBar.tsx +0 -188
  121. package/src/ui-react/components/radio/Radio.tsx +0 -216
  122. package/src/ui-react/components/select/Select.tsx +0 -109
  123. package/src/ui-react/components/switch/Switch.tsx +0 -164
  124. package/src/ui-react/components/tab-bar/TabBar.tsx +0 -137
  125. package/src/ui-react/components/textarea/Textarea.tsx +0 -109
  126. package/src/ui-react/components/toast/Toast.ts +0 -98
  127. package/src/ui-react/components/typography/Typography.tsx +0 -201
  128. package/src/ui-react/hooks/use-app-pause.js +0 -35
  129. package/src/ui-react/hooks/use-app-pause.ts +0 -33
  130. package/src/ui-react/hooks/use-app-resume.js +0 -37
  131. package/src/ui-react/hooks/use-app-resume.ts +0 -32
  132. package/src/ui-react/hooks/use-app-state.js +0 -35
  133. package/src/ui-react/hooks/use-did-hide.js +0 -25
  134. package/src/ui-react/hooks/use-did-hide.ts +0 -34
  135. package/src/ui-react/hooks/use-did-show.js +0 -26
  136. package/src/ui-react/hooks/use-did-show.ts +0 -34
  137. package/src/ui-react/hooks/use-listener-scan-qr.js +0 -33
  138. package/src/ui-react/hooks/use-listener-scan-qr.ts +0 -52
  139. package/src/ui-react/hooks/use-navigate.js +0 -15
  140. package/src/ui-react/hooks/use-navigate.ts +0 -41
  141. package/src/ui-react/hooks/use-tap-app-bar.js +0 -26
  142. package/src/ui-react/hooks/use-tap-app-bar.ts +0 -34
  143. package/src/ui-react/index.js +0 -9
  144. package/src/ui-react/styles/VETCProvider.tsx +0 -152
  145. package/src/ui-react/tokens/colors.ts +0 -91
  146. package/src/ui-react/tokens/spacing.ts +0 -59
  147. package/src/ui-react/tokens/typography.ts +0 -63
  148. package/src/ui-react/tokens_vetc.json +0 -1517
  149. package/src/ui-react/types/app.js +0 -30
  150. package/src/ui-react/types/app.ts +0 -32
  151. /package/{src/ui-react/components/avatar/index.ts → dist/components/avatar/index.d.ts} +0 -0
  152. /package/{src/ui-react/components/bottom-sheet/index.ts → dist/components/bottom-sheet/index.d.ts} +0 -0
  153. /package/{src/ui-react/components/button/index.ts → dist/components/button/index.d.ts} +0 -0
  154. /package/{src/ui-react/components/button-group/index.ts → dist/components/button-group/index.d.ts} +0 -0
  155. /package/{src/ui-react/components/card/index.ts → dist/components/card/index.d.ts} +0 -0
  156. /package/{src/ui-react/components/checkbox/index.ts → dist/components/checkbox/index.d.ts} +0 -0
  157. /package/{src/ui-react/components/chip/index.ts → dist/components/chip/index.d.ts} +0 -0
  158. /package/{src/ui-react/components/dialog/index.ts → dist/components/dialog/index.d.ts} +0 -0
  159. /package/{src/ui-react/components/divider/index.ts → dist/components/divider/index.d.ts} +0 -0
  160. /package/{src/ui-react/components/input/index.ts → dist/components/input/index.d.ts} +0 -0
  161. /package/{src/ui-react/components/list/index.ts → dist/components/list/index.d.ts} +0 -0
  162. /package/{src/ui-react/components/loading/index.ts → dist/components/loading/index.d.ts} +0 -0
  163. /package/{src/ui-react/components/modal/index.ts → dist/components/modal/index.d.ts} +0 -0
  164. /package/{src/ui-react/components/navigation-bar/index.ts → dist/components/navigation-bar/index.d.ts} +0 -0
  165. /package/{src/ui-react/components/radio/index.ts → dist/components/radio/index.d.ts} +0 -0
  166. /package/{src/ui-react/components/select/index.ts → dist/components/select/index.d.ts} +0 -0
  167. /package/{src/ui-react/components/switch/index.ts → dist/components/switch/index.d.ts} +0 -0
  168. /package/{src/ui-react/components/tab-bar/index.ts → dist/components/tab-bar/index.d.ts} +0 -0
  169. /package/{src/ui-react/components/textarea/index.ts → dist/components/textarea/index.d.ts} +0 -0
  170. /package/{src/ui-react/components/toast/index.ts → dist/components/toast/index.d.ts} +0 -0
  171. /package/{src/ui-react/components/typography/index.ts → dist/components/typography/index.d.ts} +0 -0
  172. /package/{src/ui-react/tokens/index.ts → dist/tokens/index.d.ts} +0 -0
@@ -1,188 +0,0 @@
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
-
8
- export interface NavigationBarAction {
9
- icon: React.ReactNode;
10
- label?: string;
11
- onClick?: () => void;
12
- disabled?: boolean;
13
- id?: string;
14
- }
15
-
16
- export type NavigationBarLeadingType = 'back' | 'close' | 'none';
17
-
18
- export interface NavigationBarProps {
19
- title?: React.ReactNode;
20
- /** Leading left button type */
21
- leading?: NavigationBarLeadingType;
22
- /** Custom back/close icon (overrides default) */
23
- leadingIcon?: React.ReactNode;
24
- onLeadingPress?: () => void;
25
- /** Max 2 action buttons on right */
26
- actions?: NavigationBarAction[];
27
- /** Fully custom left slot (overrides leading) */
28
- leftSlot?: React.ReactNode;
29
- /** Fully custom right slot (overrides actions) */
30
- rightSlot?: React.ReactNode;
31
- backgroundColor?: string;
32
- divider?: boolean;
33
- transparent?: boolean;
34
- className?: string;
35
- style?: React.CSSProperties;
36
- id?: string;
37
- /** @deprecated use leading="back" + onLeadingPress */
38
- showBack?: boolean;
39
- /** @deprecated use leadingIcon */
40
- backIcon?: React.ReactNode;
41
- /** @deprecated use onLeadingPress */
42
- onBack?: () => void;
43
- left?: React.ReactNode;
44
- right?: React.ReactNode;
45
- }
46
-
47
- function BackIcon() {
48
- return (
49
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
50
- <path d="M15 19L8 12L15 5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
51
- </svg>
52
- );
53
- }
54
-
55
- function CloseIcon() {
56
- return (
57
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
58
- <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
59
- </svg>
60
- );
61
- }
62
-
63
- const iconBtnStyle: React.CSSProperties = {
64
- background: 'none',
65
- border: 'none',
66
- cursor: 'pointer',
67
- width: 'var(--vetc-nav-icon-btn-size)',
68
- height: 'var(--vetc-nav-icon-btn-size)',
69
- display: 'flex',
70
- alignItems: 'center',
71
- justifyContent: 'center',
72
- borderRadius: 'var(--vetc-radius-pill)',
73
- color: 'var(--vetc-nav-icon-color)',
74
- flexShrink: 0,
75
- padding: 0,
76
- transition: 'background-color var(--vetc-transition-fast)',
77
- };
78
-
79
- export function NavigationBar({
80
- title,
81
- leading = 'none',
82
- leadingIcon,
83
- onLeadingPress,
84
- actions = [],
85
- leftSlot,
86
- rightSlot,
87
- backgroundColor,
88
- divider = true,
89
- transparent = false,
90
- className = '',
91
- style,
92
- id,
93
- // deprecated props
94
- showBack,
95
- backIcon,
96
- onBack,
97
- left,
98
- right,
99
- }: NavigationBarProps) {
100
- // Backward compat
101
- const resolvedLeading: NavigationBarLeadingType = showBack ? 'back' : leading;
102
- const resolvedLeadingIcon = leadingIcon ?? backIcon;
103
- const resolvedOnLeading = onLeadingPress ?? onBack;
104
-
105
- const renderLeadingButton = () => {
106
- if (leftSlot !== undefined || left !== undefined) return leftSlot ?? left;
107
- if (resolvedLeading === 'none') return null;
108
-
109
- const icon = resolvedLeadingIcon ?? (resolvedLeading === 'close' ? <CloseIcon /> : <BackIcon />);
110
- const label = resolvedLeading === 'close' ? 'Đóng' : 'Quay lại';
111
-
112
- return (
113
- <button onClick={resolvedOnLeading} style={iconBtnStyle} aria-label={label}>
114
- {icon}
115
- </button>
116
- );
117
- };
118
-
119
- return (
120
- <header
121
- id={id}
122
- className={`vetc-nav-bar ${className}`}
123
- style={{
124
- display: 'flex',
125
- alignItems: 'center',
126
- height: 'var(--vetc-nav-height)',
127
- padding: `0 var(--vetc-nav-padding-x)`,
128
- backgroundColor: transparent ? 'transparent' : (backgroundColor ?? 'var(--vetc-nav-bg)'),
129
- borderBottom: divider && !transparent ? `1px solid var(--vetc-nav-border)` : 'none',
130
- fontFamily: 'var(--vetc-font-family)',
131
- position: 'relative',
132
- ...style,
133
- }}
134
- >
135
- {/* Left zone — always occupies space to keep title centered */}
136
- <div style={{ display: 'flex', alignItems: 'center', minWidth: 'var(--vetc-nav-icon-btn-size)', zIndex: 1 }}>
137
- {renderLeadingButton()}
138
- </div>
139
-
140
- {/* Title — absolutely centered in the full bar */}
141
- {title && (
142
- <div style={{
143
- position: 'absolute',
144
- left: 0,
145
- right: 0,
146
- display: 'flex',
147
- alignItems: 'center',
148
- justifyContent: 'center',
149
- pointerEvents: 'none',
150
- }}>
151
- <span style={{
152
- fontSize: 'var(--vetc-nav-title-size)',
153
- fontWeight: 'var(--vetc-nav-title-weight)' as any,
154
- color: 'var(--vetc-nav-title-color)',
155
- lineHeight: 'var(--vetc-line-height-normal)',
156
- maxWidth: '60%',
157
- overflow: 'hidden',
158
- textOverflow: 'ellipsis',
159
- whiteSpace: 'nowrap',
160
- }}>
161
- {title}
162
- </span>
163
- </div>
164
- )}
165
-
166
- {/* Right zone */}
167
- <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--vetc-space-4)', marginLeft: 'auto', zIndex: 1 }}>
168
- {rightSlot !== undefined || right !== undefined
169
- ? (rightSlot ?? right)
170
- : actions.slice(0, 2).map((action, i) => (
171
- <button
172
- key={i}
173
- id={action.id}
174
- onClick={action.onClick}
175
- disabled={action.disabled}
176
- aria-label={action.label}
177
- style={{ ...iconBtnStyle, opacity: action.disabled ? 0.4 : 1 }}
178
- >
179
- {action.icon}
180
- </button>
181
- ))
182
- }
183
- </div>
184
- </header>
185
- );
186
- }
187
-
188
- export default NavigationBar;
@@ -1,216 +0,0 @@
1
- /**
2
- * VETC Radio & RadioGroup
3
- * Figma: size=20px, states: default/hover/selected/disabled
4
- * variant: neutral (gray-90) | brand (green-40)
5
- */
6
- import React, { useState } from 'react';
7
-
8
- export type RadioVariant = 'neutral' | 'brand';
9
-
10
- export interface RadioProps {
11
- label?: React.ReactNode;
12
- description?: React.ReactNode;
13
- checked?: boolean;
14
- defaultChecked?: boolean;
15
- disabled?: boolean;
16
- variant?: RadioVariant;
17
- onChange?: (checked: boolean) => void;
18
- value?: any;
19
- id?: string;
20
- className?: string;
21
- style?: React.CSSProperties;
22
- }
23
-
24
- export function Radio({
25
- label,
26
- description,
27
- checked,
28
- defaultChecked = false,
29
- disabled = false,
30
- variant = 'brand',
31
- onChange,
32
- value,
33
- id,
34
- className = '',
35
- style,
36
- }: RadioProps) {
37
- const [internalChecked, setInternalChecked] = useState(defaultChecked);
38
- const [hovered, setHovered] = useState(false);
39
-
40
- const isControlled = checked !== undefined;
41
- const isChecked = isControlled ? checked : internalChecked;
42
-
43
- const handleChange = () => {
44
- if (disabled || isChecked) return;
45
- if (!isControlled) setInternalChecked(true);
46
- onChange?.(true);
47
- };
48
-
49
- const accentColor = variant === 'brand'
50
- ? 'var(--vetc-color-brand)'
51
- : 'var(--vetc-gray-90)';
52
- const accentHover = variant === 'brand'
53
- ? 'var(--vetc-color-brand-hover)'
54
- : 'var(--vetc-gray-70)';
55
-
56
- const ringColor = disabled
57
- ? 'var(--vetc-color-border-disabled)'
58
- : isChecked
59
- ? (hovered ? accentHover : accentColor)
60
- : (hovered ? accentColor : 'var(--vetc-color-border)');
61
-
62
- const dotColor = disabled ? 'var(--vetc-color-text-disabled)' : accentColor;
63
-
64
- return (
65
- <label
66
- htmlFor={id}
67
- className={`vetc-radio ${className}`}
68
- onMouseEnter={() => !disabled && setHovered(true)}
69
- onMouseLeave={() => setHovered(false)}
70
- style={{
71
- display: 'inline-flex',
72
- alignItems: description ? 'flex-start' : 'center',
73
- gap: 'var(--vetc-space-12)',
74
- cursor: disabled ? 'not-allowed' : 'pointer',
75
- userSelect: 'none',
76
- fontFamily: 'var(--vetc-font-family)',
77
- ...style,
78
- }}
79
- >
80
- <input
81
- id={id}
82
- type="radio"
83
- checked={isChecked}
84
- disabled={disabled}
85
- value={value}
86
- onChange={handleChange}
87
- style={{ position: 'absolute', opacity: 0, width: 0, height: 0, pointerEvents: 'none' }}
88
- />
89
-
90
- {/* Visual radio ring */}
91
- <span
92
- aria-hidden="true"
93
- style={{
94
- width: '20px',
95
- height: '20px',
96
- flexShrink: 0,
97
- borderRadius: '50%',
98
- border: `1.5px solid ${ringColor}`,
99
- backgroundColor: 'transparent',
100
- display: 'flex',
101
- alignItems: 'center',
102
- justifyContent: 'center',
103
- transition: 'border-color var(--vetc-transition-fast)',
104
- marginTop: description ? '2px' : 0,
105
- }}
106
- >
107
- {isChecked && (
108
- <span style={{
109
- width: '8px',
110
- height: '8px',
111
- borderRadius: '50%',
112
- backgroundColor: dotColor,
113
- flexShrink: 0,
114
- }} />
115
- )}
116
- </span>
117
-
118
- {/* Label + description */}
119
- {(label || description) && (
120
- <span style={{ display: 'flex', flexDirection: 'column', gap: 'var(--vetc-space-2)' }}>
121
- {label && (
122
- <span style={{
123
- fontSize: 'var(--vetc-font-size-base)',
124
- fontWeight: 'var(--vetc-font-weight-regular)' as any,
125
- lineHeight: 'var(--vetc-line-height-relaxed)',
126
- color: disabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-color-text-primary)',
127
- }}>
128
- {label}
129
- </span>
130
- )}
131
- {description && (
132
- <span style={{
133
- fontSize: 'var(--vetc-font-size-sm)',
134
- fontWeight: 'var(--vetc-font-weight-regular)' as any,
135
- lineHeight: 'var(--vetc-line-height-relaxed)',
136
- color: disabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-color-text-secondary)',
137
- }}>
138
- {description}
139
- </span>
140
- )}
141
- </span>
142
- )}
143
- </label>
144
- );
145
- }
146
-
147
- // ── RadioGroup ────────────────────────────────────────────────────────────────
148
- export interface RadioOption {
149
- label: React.ReactNode;
150
- description?: React.ReactNode;
151
- value: any;
152
- disabled?: boolean;
153
- }
154
-
155
- export interface RadioGroupProps {
156
- options: RadioOption[];
157
- value?: any;
158
- defaultValue?: any;
159
- direction?: 'horizontal' | 'vertical';
160
- disabled?: boolean;
161
- variant?: RadioVariant;
162
- onChange?: (value: any) => void;
163
- className?: string;
164
- style?: React.CSSProperties;
165
- }
166
-
167
- export function RadioGroup({
168
- options,
169
- value,
170
- defaultValue,
171
- direction = 'vertical',
172
- disabled = false,
173
- variant = 'brand',
174
- onChange,
175
- className = '',
176
- style,
177
- }: RadioGroupProps) {
178
- const [internalValue, setInternalValue] = useState(defaultValue);
179
- const isControlled = value !== undefined;
180
- const currentValue = isControlled ? value : internalValue;
181
-
182
- const handleChange = (optValue: any) => {
183
- if (!isControlled) setInternalValue(optValue);
184
- onChange?.(optValue);
185
- };
186
-
187
- return (
188
- <div
189
- role="radiogroup"
190
- className={`vetc-radio-group ${className}`}
191
- style={{
192
- display: 'flex',
193
- flexDirection: direction === 'vertical' ? 'column' : 'row',
194
- flexWrap: direction === 'horizontal' ? 'wrap' : undefined,
195
- gap: direction === 'vertical' ? 'var(--vetc-space-12)' : 'var(--vetc-space-16)',
196
- fontFamily: 'var(--vetc-font-family)',
197
- ...style,
198
- }}
199
- >
200
- {options.map((opt) => (
201
- <Radio
202
- key={String(opt.value)}
203
- label={opt.label}
204
- description={opt.description}
205
- checked={currentValue === opt.value}
206
- disabled={opt.disabled ?? disabled}
207
- variant={variant}
208
- value={opt.value}
209
- onChange={() => handleChange(opt.value)}
210
- />
211
- ))}
212
- </div>
213
- );
214
- }
215
-
216
- export default Radio;
@@ -1,109 +0,0 @@
1
- /**
2
- * VETC Select — tokenized
3
- */
4
- import React from 'react';
5
- import { Select as AntSelect } from 'antd';
6
- import type { DefaultOptionType } from 'antd/es/select';
7
-
8
- export type SelectOption = DefaultOptionType;
9
-
10
- export interface SelectProps {
11
- label?: string;
12
- placeholder?: string;
13
- value?: string | string[] | number | number[];
14
- defaultValue?: string | string[] | number | number[];
15
- options?: SelectOption[];
16
- helperText?: string;
17
- error?: string;
18
- disabled?: boolean;
19
- multiple?: boolean;
20
- searchable?: boolean;
21
- allowClear?: boolean;
22
- loading?: boolean;
23
- onChange?: (value: any, option: any) => void;
24
- onSearch?: (value: string) => void;
25
- id?: string;
26
- className?: string;
27
- style?: React.CSSProperties;
28
- required?: boolean;
29
- popupMatchSelectWidth?: boolean;
30
- }
31
-
32
- export function Select({
33
- label, placeholder, value, defaultValue,
34
- options = [], helperText, error,
35
- disabled = false, multiple = false, searchable = false,
36
- allowClear = false, loading = false,
37
- onChange, onSearch, id, className = '', style, required = false,
38
- popupMatchSelectWidth = true,
39
- }: SelectProps) {
40
- const hasError = !!error;
41
- const helperMsg = error ?? helperText;
42
- const helperColor = hasError
43
- ? 'var(--vetc-input-helper-color-error)'
44
- : 'var(--vetc-input-helper-color)';
45
-
46
- return (
47
- <div
48
- className={`vetc-select-wrapper ${className}`}
49
- style={{ display: 'flex', flexDirection: 'column', gap: 'var(--vetc-input-gap)', ...style }}
50
- >
51
- {label && (
52
- <label
53
- htmlFor={id}
54
- style={{
55
- fontFamily: 'var(--vetc-font-family)',
56
- fontSize: 'var(--vetc-input-label-font-size)',
57
- fontWeight: 'var(--vetc-input-label-font-weight)' as any,
58
- lineHeight: 'var(--vetc-line-height-relaxed)',
59
- color: disabled
60
- ? 'var(--vetc-color-text-disabled)'
61
- : 'var(--vetc-input-label-color)',
62
- display: 'block',
63
- }}
64
- >
65
- {label}
66
- {required && (
67
- <span style={{ color: 'var(--vetc-color-negative)', marginLeft: 'var(--vetc-space-2)' }}>*</span>
68
- )}
69
- </label>
70
- )}
71
-
72
- <AntSelect
73
- id={id}
74
- value={value}
75
- defaultValue={defaultValue}
76
- placeholder={placeholder}
77
- options={options}
78
- disabled={disabled}
79
- mode={multiple ? 'multiple' : undefined}
80
- showSearch={searchable}
81
- allowClear={allowClear}
82
- loading={loading}
83
- status={hasError ? 'error' : undefined}
84
- onChange={onChange}
85
- onSearch={onSearch}
86
- popupMatchSelectWidth={popupMatchSelectWidth}
87
- style={{
88
- height: 'var(--vetc-input-height)',
89
- width: '100%',
90
- fontFamily: 'var(--vetc-font-family)',
91
- fontSize: 'var(--vetc-input-font-size)',
92
- }}
93
- />
94
-
95
- {helperMsg && (
96
- <span style={{
97
- fontFamily: 'var(--vetc-font-family)',
98
- fontSize: 'var(--vetc-input-helper-font-size)',
99
- lineHeight: 'var(--vetc-line-height-relaxed)',
100
- color: helperColor,
101
- }}>
102
- {helperMsg}
103
- </span>
104
- )}
105
- </div>
106
- );
107
- }
108
-
109
- export default Select;
@@ -1,164 +0,0 @@
1
- /**
2
- * VETC Switch
3
- * Figma: w=52px h=32px, thumb=28px, ON=brand-green, OFF=gray-30
4
- * variant: neutral (gray-90 on) | brand (green-40 on)
5
- */
6
- import React, { useState } from 'react';
7
-
8
- export type SwitchVariant = 'neutral' | 'brand';
9
-
10
- export interface SwitchProps {
11
- label?: React.ReactNode;
12
- description?: React.ReactNode;
13
- labelPosition?: 'left' | 'right';
14
- checked?: boolean;
15
- defaultChecked?: boolean;
16
- disabled?: boolean;
17
- loading?: boolean;
18
- variant?: SwitchVariant;
19
- onChange?: (checked: boolean) => void;
20
- id?: string;
21
- className?: string;
22
- style?: React.CSSProperties;
23
- }
24
-
25
- export function Switch({
26
- label,
27
- description,
28
- labelPosition = 'right',
29
- checked,
30
- defaultChecked = false,
31
- disabled = false,
32
- loading = false,
33
- variant = 'brand',
34
- onChange,
35
- id,
36
- className = '',
37
- style,
38
- }: SwitchProps) {
39
- const [internalChecked, setInternalChecked] = useState(defaultChecked);
40
-
41
- const isControlled = checked !== undefined;
42
- const isChecked = isControlled ? checked : internalChecked;
43
- const isDisabled = disabled || loading;
44
-
45
- const handleToggle = () => {
46
- if (isDisabled) return;
47
- const next = !isChecked;
48
- if (!isControlled) setInternalChecked(next);
49
- onChange?.(next);
50
- };
51
-
52
- const onColor = variant === 'brand'
53
- ? 'var(--vetc-switch-on-bg)'
54
- : 'var(--vetc-gray-90)';
55
-
56
- const trackBg = isDisabled
57
- ? 'var(--vetc-color-bg-disabled)'
58
- : isChecked
59
- ? onColor
60
- : 'var(--vetc-switch-off-bg)';
61
-
62
- const thumbLeft = isChecked ? 'calc(100% - 30px)' : '2px';
63
-
64
- const switchEl = (
65
- <button
66
- id={id}
67
- type="button"
68
- role="switch"
69
- aria-checked={isChecked}
70
- disabled={isDisabled}
71
- onClick={handleToggle}
72
- className={`vetc-switch ${className}`}
73
- style={{
74
- position: 'relative',
75
- display: 'inline-flex',
76
- alignItems: 'center',
77
- width: 'var(--vetc-switch-width)',
78
- height: 'var(--vetc-switch-height)',
79
- borderRadius: 'var(--vetc-radius-pill)',
80
- backgroundColor: trackBg,
81
- border: 'none',
82
- cursor: isDisabled ? 'not-allowed' : 'pointer',
83
- padding: 0,
84
- flexShrink: 0,
85
- transition: 'background-color var(--vetc-transition-fast)',
86
- outline: 'none',
87
- ...(label || description ? {} : style),
88
- }}
89
- >
90
- {/* Thumb */}
91
- <span style={{
92
- position: 'absolute',
93
- left: thumbLeft,
94
- width: 'var(--vetc-switch-thumb-size)',
95
- height: 'var(--vetc-switch-thumb-size)',
96
- borderRadius: '50%',
97
- backgroundColor: isDisabled ? 'var(--vetc-gray-20)' : 'var(--vetc-white)',
98
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
99
- transition: 'left var(--vetc-transition-fast)',
100
- display: 'flex',
101
- alignItems: 'center',
102
- justifyContent: 'center',
103
- }}>
104
- {loading && (
105
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ animation: 'vetc-btn-spin 0.6s linear infinite' }} aria-hidden="true">
106
- <circle cx="6" cy="6" r="4" stroke="var(--vetc-gray-30)" strokeWidth="1.5" />
107
- <path d="M6 2a4 4 0 014 4" stroke="var(--vetc-color-brand)" strokeWidth="1.5" strokeLinecap="round" />
108
- </svg>
109
- )}
110
- </span>
111
- <style>{`@keyframes vetc-btn-spin { to { transform: rotate(360deg); } }`}</style>
112
- </button>
113
- );
114
-
115
- if (!label && !description) return switchEl;
116
-
117
- const labelBlock = (
118
- <span style={{ display: 'flex', flexDirection: 'column', gap: 'var(--vetc-space-2)', flex: 1 }}>
119
- {label && (
120
- <span style={{
121
- fontSize: 'var(--vetc-font-size-base)',
122
- fontWeight: 'var(--vetc-font-weight-regular)' as any,
123
- lineHeight: 'var(--vetc-line-height-relaxed)',
124
- color: isDisabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-color-text-primary)',
125
- }}>
126
- {label}
127
- </span>
128
- )}
129
- {description && (
130
- <span style={{
131
- fontSize: 'var(--vetc-font-size-sm)',
132
- fontWeight: 'var(--vetc-font-weight-regular)' as any,
133
- lineHeight: 'var(--vetc-line-height-relaxed)',
134
- color: isDisabled ? 'var(--vetc-color-text-disabled)' : 'var(--vetc-color-text-secondary)',
135
- }}>
136
- {description}
137
- </span>
138
- )}
139
- </span>
140
- );
141
-
142
- return (
143
- <div
144
- style={{
145
- display: 'flex',
146
- alignItems: 'center',
147
- gap: 'var(--vetc-space-12)',
148
- flexDirection: labelPosition === 'left' ? 'row-reverse' : 'row',
149
- fontFamily: 'var(--vetc-font-family)',
150
- cursor: isDisabled ? 'not-allowed' : 'pointer',
151
- ...style,
152
- }}
153
- onClick={handleToggle}
154
- >
155
- {labelPosition === 'right' ? (
156
- <>{switchEl}{labelBlock}</>
157
- ) : (
158
- <>{labelBlock}{switchEl}</>
159
- )}
160
- </div>
161
- );
162
- }
163
-
164
- export default Switch;