@umbra.ui/core 0.1.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 (272) hide show
  1. package/dist/components/controls/Dropdown/types.d.ts +5 -0
  2. package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
  3. package/dist/components/controls/Dropdown/types.js +1 -0
  4. package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
  5. package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
  6. package/dist/components/controls/SegmentedControl/types.js +1 -0
  7. package/dist/components/dialogs/Alert/types.d.ts +7 -0
  8. package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
  9. package/dist/components/dialogs/Alert/types.js +1 -0
  10. package/dist/components/dialogs/Toast/types.d.ts +34 -0
  11. package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
  12. package/dist/components/dialogs/Toast/types.js +10 -0
  13. package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
  14. package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
  15. package/dist/components/dialogs/Toast/useToast.js +90 -0
  16. package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
  17. package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
  18. package/dist/components/indicators/Tooltip/tooltip.js +33 -0
  19. package/dist/components/indicators/Tooltip/types.d.ts +14 -0
  20. package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
  21. package/dist/components/indicators/Tooltip/types.js +1 -0
  22. package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
  23. package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
  24. package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
  25. package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
  26. package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
  27. package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
  28. package/dist/components/inputs/Tags/types.d.ts +93 -0
  29. package/dist/components/inputs/Tags/types.d.ts.map +1 -0
  30. package/dist/components/inputs/Tags/types.js +216 -0
  31. package/dist/components/inputs/search/types.d.ts +9 -0
  32. package/dist/components/inputs/search/types.d.ts.map +1 -0
  33. package/dist/components/inputs/search/types.js +1 -0
  34. package/dist/components/navigation/adaptive/types.d.ts +16 -0
  35. package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
  36. package/dist/components/navigation/adaptive/types.js +1 -0
  37. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
  38. package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
  39. package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
  40. package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
  41. package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
  42. package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
  43. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
  44. package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
  45. package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
  46. package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
  47. package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
  48. package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
  49. package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
  50. package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
  51. package/dist/components/navigation/adaptive/useViewResize.js +146 -0
  52. package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
  53. package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
  54. package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
  55. package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
  56. package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
  57. package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
  58. package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
  59. package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
  60. package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
  61. package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
  62. package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
  63. package/dist/components/navigation/tabcontroller/types.js +1 -0
  64. package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
  65. package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
  66. package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
  67. package/dist/components/navigation/types.d.ts +8 -0
  68. package/dist/components/navigation/types.d.ts.map +1 -0
  69. package/dist/components/navigation/types.js +1 -0
  70. package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
  71. package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
  72. package/dist/components/pickers/CollectionPicker/types.js +1 -0
  73. package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
  74. package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
  75. package/dist/components/pickers/ColorPicker/colors.js +266 -0
  76. package/dist/components/pickers/FilePicker/types.d.ts +10 -0
  77. package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
  78. package/dist/components/pickers/FilePicker/types.js +1 -0
  79. package/dist/index.d.ts +91 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +196 -0
  82. package/dist/theme.d.ts +73 -0
  83. package/dist/theme.d.ts.map +1 -0
  84. package/dist/theme.js +279 -0
  85. package/dist/themes/blank.d.ts +7 -0
  86. package/dist/themes/blank.d.ts.map +1 -0
  87. package/dist/themes/blank.js +543 -0
  88. package/dist/themes/crimson-dark.d.ts +4 -0
  89. package/dist/themes/crimson-dark.d.ts.map +1 -0
  90. package/dist/themes/crimson-dark.js +552 -0
  91. package/dist/themes/cyan-light.d.ts +4 -0
  92. package/dist/themes/cyan-light.d.ts.map +1 -0
  93. package/dist/themes/cyan-light.js +552 -0
  94. package/dist/themes/dark.d.ts +4 -0
  95. package/dist/themes/dark.d.ts.map +1 -0
  96. package/dist/themes/dark.js +551 -0
  97. package/dist/themes/gold-dark.d.ts +4 -0
  98. package/dist/themes/gold-dark.d.ts.map +1 -0
  99. package/dist/themes/gold-dark.js +552 -0
  100. package/dist/themes/grass-dark.d.ts +4 -0
  101. package/dist/themes/grass-dark.d.ts.map +1 -0
  102. package/dist/themes/grass-dark.js +552 -0
  103. package/dist/themes/indigo.d.ts +4 -0
  104. package/dist/themes/indigo.d.ts.map +1 -0
  105. package/dist/themes/indigo.js +552 -0
  106. package/dist/themes/light.d.ts +4 -0
  107. package/dist/themes/light.d.ts.map +1 -0
  108. package/dist/themes/light.js +551 -0
  109. package/dist/themes/orange-dark.d.ts +4 -0
  110. package/dist/themes/orange-dark.d.ts.map +1 -0
  111. package/dist/themes/orange-dark.js +551 -0
  112. package/dist/themes/orange-light.d.ts +4 -0
  113. package/dist/themes/orange-light.d.ts.map +1 -0
  114. package/dist/themes/orange-light.js +551 -0
  115. package/package.json +62 -0
  116. package/src/components/controls/Button/Button.vue +417 -0
  117. package/src/components/controls/Button/README.md +348 -0
  118. package/src/components/controls/Button/theme.css +200 -0
  119. package/src/components/controls/Checkbox/Checkbox.vue +164 -0
  120. package/src/components/controls/Checkbox/README.md +441 -0
  121. package/src/components/controls/Checkbox/theme.css +36 -0
  122. package/src/components/controls/Dropdown/Dropdown.vue +476 -0
  123. package/src/components/controls/Dropdown/README.md +370 -0
  124. package/src/components/controls/Dropdown/theme.css +50 -0
  125. package/src/components/controls/Dropdown/types.ts +6 -0
  126. package/src/components/controls/IconButton/IconButton.vue +267 -0
  127. package/src/components/controls/IconButton/README.md +502 -0
  128. package/src/components/controls/IconButton/theme.css +89 -0
  129. package/src/components/controls/Radio/README.md +591 -0
  130. package/src/components/controls/Radio/Radio.vue +89 -0
  131. package/src/components/controls/Radio/theme.css +14 -0
  132. package/src/components/controls/RangeSlider/README.md +608 -0
  133. package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
  134. package/src/components/controls/RangeSlider/theme.css +80 -0
  135. package/src/components/controls/SegmentedControl/README.md +587 -0
  136. package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
  137. package/src/components/controls/SegmentedControl/theme.css +60 -0
  138. package/src/components/controls/SegmentedControl/types.ts +5 -0
  139. package/src/components/controls/Slider/README.md +627 -0
  140. package/src/components/controls/Slider/Slider.vue +260 -0
  141. package/src/components/controls/Slider/theme.css +74 -0
  142. package/src/components/controls/Stepper/README.md +601 -0
  143. package/src/components/controls/Stepper/Stepper.vue +103 -0
  144. package/src/components/controls/Stepper/theme.css +53 -0
  145. package/src/components/controls/Switch/README.md +667 -0
  146. package/src/components/controls/Switch/Switch.vue +127 -0
  147. package/src/components/controls/Switch/theme.css +42 -0
  148. package/src/components/dialogs/Alert/Alert.vue +218 -0
  149. package/src/components/dialogs/Alert/README.md +450 -0
  150. package/src/components/dialogs/Alert/theme.css +44 -0
  151. package/src/components/dialogs/Alert/types.ts +11 -0
  152. package/src/components/dialogs/Toast/README.md +522 -0
  153. package/src/components/dialogs/Toast/Toast.vue +296 -0
  154. package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
  155. package/src/components/dialogs/Toast/theme.css +44 -0
  156. package/src/components/dialogs/Toast/types.ts +46 -0
  157. package/src/components/dialogs/Toast/useToast.ts +127 -0
  158. package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
  159. package/src/components/indicators/ProgressBar/README.md +744 -0
  160. package/src/components/indicators/ProgressBar/theme.css +36 -0
  161. package/src/components/indicators/Tooltip/README.md +723 -0
  162. package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
  163. package/src/components/indicators/Tooltip/theme.css +18 -0
  164. package/src/components/indicators/Tooltip/tooltip.ts +48 -0
  165. package/src/components/indicators/Tooltip/types.ts +15 -0
  166. package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
  167. package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
  168. package/src/components/inputs/AutogrowTextView/README.md +643 -0
  169. package/src/components/inputs/AutogrowTextView/theme.css +28 -0
  170. package/src/components/inputs/InputCard/InputCard.vue +600 -0
  171. package/src/components/inputs/InputCard/README.md +636 -0
  172. package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
  173. package/src/components/inputs/InputEmail/README.md +764 -0
  174. package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
  175. package/src/components/inputs/InputNumber/README.md +749 -0
  176. package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
  177. package/src/components/inputs/InputPhone/README.md +636 -0
  178. package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
  179. package/src/components/inputs/InputSecure/README.md +771 -0
  180. package/src/components/inputs/InputText/InputText.vue +225 -0
  181. package/src/components/inputs/InputText/README.md +844 -0
  182. package/src/components/inputs/OTP/OTP.vue +349 -0
  183. package/src/components/inputs/OTP/README.md +736 -0
  184. package/src/components/inputs/OTP/theme.css +50 -0
  185. package/src/components/inputs/StringCapture/README.md +718 -0
  186. package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
  187. package/src/components/inputs/StringCapture/theme.css +86 -0
  188. package/src/components/inputs/Tags/README.md +897 -0
  189. package/src/components/inputs/Tags/TagBar.vue +793 -0
  190. package/src/components/inputs/Tags/TagCreation.vue +219 -0
  191. package/src/components/inputs/Tags/TagPicker.vue +380 -0
  192. package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
  193. package/src/components/inputs/Tags/theme.css +121 -0
  194. package/src/components/inputs/Tags/types.ts +346 -0
  195. package/src/components/inputs/search/README.md +759 -0
  196. package/src/components/inputs/search/SearchBar.vue +394 -0
  197. package/src/components/inputs/search/SearchResults.vue +310 -0
  198. package/src/components/inputs/search/theme.css +187 -0
  199. package/src/components/inputs/search/types.ts +8 -0
  200. package/src/components/inputs/theme.css +102 -0
  201. package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
  202. package/src/components/menus/ActionMenu/README.md +825 -0
  203. package/src/components/menus/ActionMenu/theme.css +93 -0
  204. package/src/components/models/Popover/Popover.vue +551 -0
  205. package/src/components/models/Popover/README.md +885 -0
  206. package/src/components/models/Popover/theme.css +52 -0
  207. package/src/components/models/Sheet/README.md +1159 -0
  208. package/src/components/models/Sheet/Sheet.vue +465 -0
  209. package/src/components/models/Sheet/theme.css +72 -0
  210. package/src/components/models/Sidebar/README.md +1228 -0
  211. package/src/components/models/Sidebar/Sidebar.vue +480 -0
  212. package/src/components/models/Sidebar/theme.css +90 -0
  213. package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
  214. package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
  215. package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
  216. package/src/components/navigation/adaptive/README.md +768 -0
  217. package/src/components/navigation/adaptive/types.ts +19 -0
  218. package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
  219. package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
  220. package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
  221. package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
  222. package/src/components/navigation/adaptive/useViewResize.ts +211 -0
  223. package/src/components/navigation/navstack/NavigationStack.vue +180 -0
  224. package/src/components/navigation/navstack/README.md +994 -0
  225. package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
  226. package/src/components/navigation/slideover/README.md +1275 -0
  227. package/src/components/navigation/slideover/SlideoverController.vue +287 -0
  228. package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
  229. package/src/components/navigation/splitview/README.md +1115 -0
  230. package/src/components/navigation/splitview/SplitViewController.vue +176 -0
  231. package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
  232. package/src/components/navigation/tabcontroller/README.md +919 -0
  233. package/src/components/navigation/tabcontroller/TabController.vue +307 -0
  234. package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
  235. package/src/components/navigation/tabcontroller/types.ts +24 -0
  236. package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
  237. package/src/components/navigation/theme.css +91 -0
  238. package/src/components/navigation/types.ts +7 -0
  239. package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
  240. package/src/components/pickers/CollectionPicker/README.md +1115 -0
  241. package/src/components/pickers/CollectionPicker/theme.css +14 -0
  242. package/src/components/pickers/CollectionPicker/types.ts +11 -0
  243. package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
  244. package/src/components/pickers/ColorPicker/README.md +1439 -0
  245. package/src/components/pickers/ColorPicker/colors.ts +299 -0
  246. package/src/components/pickers/ColorPicker/theme.css +32 -0
  247. package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
  248. package/src/components/pickers/DatePicker/README.md +1195 -0
  249. package/src/components/pickers/DatePicker/theme.css +22 -0
  250. package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
  251. package/src/components/pickers/FilePicker/README.md +1542 -0
  252. package/src/components/pickers/FilePicker/theme.css +48 -0
  253. package/src/components/pickers/FilePicker/types.ts +10 -0
  254. package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
  255. package/src/components/pickers/IconPicker/README.md +1161 -0
  256. package/src/components/pickers/IconPicker/theme.css +28 -0
  257. package/src/components/pickers/theme.css +82 -0
  258. package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
  259. package/src/components/views/MarkdownViewer/README.md +833 -0
  260. package/src/components/views/MarkdownViewer/theme.css +130 -0
  261. package/src/index.ts +263 -0
  262. package/src/theme.ts +378 -0
  263. package/src/themes/crimson-dark.ts +556 -0
  264. package/src/themes/cyan-light.ts +556 -0
  265. package/src/themes/dark.ts +557 -0
  266. package/src/themes/gold-dark.ts +556 -0
  267. package/src/themes/grass-dark.ts +556 -0
  268. package/src/themes/indigo.ts +556 -0
  269. package/src/themes/light.ts +557 -0
  270. package/src/themes/orange-dark.ts +557 -0
  271. package/src/themes/orange-light.ts +557 -0
  272. package/src/vue.d.ts +45 -0
@@ -0,0 +1,600 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, computed } from "vue";
3
+ import "../theme.css";
4
+ import {
5
+ CreditCardIcon,
6
+ TriangleWarningIcon,
7
+ CircleCheckIcon,
8
+ } from "@umbra-ui/icons";
9
+
10
+ export interface Props {
11
+ value?: string;
12
+ placeholder?: string;
13
+ showCardType?: boolean;
14
+ state?: "normal" | "active" | "disabled" | "readonly" | "error";
15
+ }
16
+
17
+ const props = withDefaults(defineProps<Props>(), {
18
+ value: "",
19
+ placeholder: "4242 4242 4242 4242",
20
+ showCardType: true,
21
+ state: "normal",
22
+ });
23
+
24
+ const emit = defineEmits<{
25
+ "update:value": [value: string];
26
+ "update:formatted": [value: string];
27
+ "update:cardType": [value: string];
28
+ "update:valid": [value: boolean];
29
+ }>();
30
+
31
+ // Get the appropriate text color for icons based on state
32
+ const getIconColor = () => {
33
+ return "var(--input-text-filled)";
34
+ };
35
+
36
+ const currentDigitCount = computed(() => {
37
+ return internalValue.value.replace(/\D/g, "").length;
38
+ });
39
+
40
+ const maxAllowedDigits = computed(() => {
41
+ if (cardInfo.value) {
42
+ return Math.max(...cardInfo.value.lengths);
43
+ }
44
+ // Default max for unknown cards
45
+ return 16;
46
+ });
47
+
48
+ const internalValue = ref(props.value);
49
+ const cursorPosition = ref<number | null>(null);
50
+ const inputRef = ref<HTMLInputElement | null>(null);
51
+
52
+ // Card type patterns
53
+ const cardPatterns = {
54
+ visa: {
55
+ pattern: /^4/,
56
+ lengths: [16, 19],
57
+ format: [4, 4, 4, 4],
58
+ cvvLength: 3,
59
+ name: "Visa",
60
+ color: "#1A1F71",
61
+ },
62
+ mastercard: {
63
+ pattern: /^(5[1-5]|2[2-7])/,
64
+ lengths: [16],
65
+ format: [4, 4, 4, 4],
66
+ cvvLength: 3,
67
+ name: "Mastercard",
68
+ color: "#EB001B",
69
+ },
70
+ amex: {
71
+ pattern: /^3[47]/,
72
+ lengths: [15],
73
+ format: [4, 6, 5],
74
+ cvvLength: 4,
75
+ name: "American Express",
76
+ color: "#006FCF",
77
+ },
78
+ discover: {
79
+ pattern: /^(6011|65|64[4-9])/,
80
+ lengths: [16],
81
+ format: [4, 4, 4, 4],
82
+ cvvLength: 3,
83
+ name: "Discover",
84
+ color: "#FF6000",
85
+ },
86
+ diners: {
87
+ pattern: /^(36|38|30[0-5])/,
88
+ lengths: [14],
89
+ format: [4, 6, 4],
90
+ cvvLength: 3,
91
+ name: "Diners Club",
92
+ color: "#0079BE",
93
+ },
94
+ jcb: {
95
+ pattern: /^35/,
96
+ lengths: [16],
97
+ format: [4, 4, 4, 4],
98
+ cvvLength: 3,
99
+ name: "JCB",
100
+ color: "#0E4C96",
101
+ },
102
+ };
103
+
104
+ // Detect card type from number
105
+ const detectCardType = (number: string): keyof typeof cardPatterns | null => {
106
+ const cleaned = number.replace(/\s/g, "");
107
+
108
+ for (const [type, config] of Object.entries(cardPatterns)) {
109
+ if (config.pattern.test(cleaned)) {
110
+ return type as keyof typeof cardPatterns;
111
+ }
112
+ }
113
+
114
+ return null;
115
+ };
116
+
117
+ // Format card number based on type
118
+ const formatCardNumber = (
119
+ value: string
120
+ ): { formatted: string; cursorPos: number } => {
121
+ // Remove all non-digits
122
+ const cleaned = value.replace(/\D/g, "");
123
+
124
+ if (cleaned.length === 0) return { formatted: "", cursorPos: 0 };
125
+
126
+ const cardType = detectCardType(cleaned);
127
+ const format = cardType ? cardPatterns[cardType].format : [4, 4, 4, 4];
128
+
129
+ let formatted = "";
130
+ let digitIndex = 0;
131
+
132
+ for (let i = 0; i < format.length && digitIndex < cleaned.length; i++) {
133
+ const groupLength = format[i];
134
+ for (let j = 0; j < groupLength && digitIndex < cleaned.length; j++) {
135
+ formatted += cleaned[digitIndex];
136
+ digitIndex++;
137
+ }
138
+ if (digitIndex < cleaned.length) {
139
+ formatted += " ";
140
+ }
141
+ }
142
+
143
+ return { formatted, cursorPos: formatted.length };
144
+ };
145
+
146
+ // Luhn algorithm for card validation
147
+ const isValidCardNumber = (number: string): boolean => {
148
+ const cleaned = number.replace(/\D/g, "");
149
+
150
+ if (cleaned.length === 0) return true; // Empty is valid
151
+
152
+ const cardType = detectCardType(cleaned);
153
+ if (!cardType) return false;
154
+
155
+ const validLengths = cardPatterns[cardType].lengths;
156
+ if (!validLengths.includes(cleaned.length)) return false;
157
+
158
+ // Luhn algorithm
159
+ let sum = 0;
160
+ let isEven = false;
161
+
162
+ for (let i = cleaned.length - 1; i >= 0; i--) {
163
+ let digit = parseInt(cleaned[i], 10);
164
+
165
+ if (isEven) {
166
+ digit *= 2;
167
+ if (digit > 9) {
168
+ digit -= 9;
169
+ }
170
+ }
171
+
172
+ sum += digit;
173
+ isEven = !isEven;
174
+ }
175
+
176
+ return sum % 10 === 0;
177
+ };
178
+
179
+ // Computed properties
180
+ const cardType = computed(() => detectCardType(internalValue.value));
181
+ const cardInfo = computed(() =>
182
+ cardType.value ? cardPatterns[cardType.value] : null
183
+ );
184
+ const isValid = computed(() => isValidCardNumber(internalValue.value));
185
+ const isComplete = computed(() => {
186
+ const cleaned = internalValue.value.replace(/\D/g, "");
187
+ if (!cardInfo.value) return false;
188
+ return cardInfo.value.lengths.includes(cleaned.length);
189
+ });
190
+
191
+ // Override state to show error when card is complete but invalid
192
+ const effectiveState = computed(() => {
193
+ if (isComplete.value && !isValid.value) {
194
+ return "error";
195
+ }
196
+ return props.state;
197
+ });
198
+
199
+ // Watch for changes to value prop
200
+ watch(
201
+ () => props.value,
202
+ (newValue) => {
203
+ if (newValue !== internalValue.value) {
204
+ const { formatted } = formatCardNumber(newValue);
205
+ internalValue.value = formatted;
206
+ }
207
+ }
208
+ );
209
+
210
+ // Prevent non-numeric input
211
+ const handleBeforeInput = (event: Event) => {
212
+ const inputEvent = event as InputEvent;
213
+ const data = inputEvent.data;
214
+
215
+ // Allow deletion operations
216
+ if (!data) return;
217
+
218
+ // Check if we're at max digits for the detected card type
219
+ if (currentDigitCount.value >= maxAllowedDigits.value) {
220
+ event.preventDefault();
221
+ return;
222
+ }
223
+
224
+ // Only allow digits
225
+ const isDigit = /^[0-9]+$/;
226
+ if (!isDigit.test(data)) {
227
+ event.preventDefault();
228
+ }
229
+ };
230
+
231
+ // Handle input
232
+ // Update handleInput to enforce limits
233
+ const handleInput = (event: Event) => {
234
+ const target = event.target as HTMLInputElement;
235
+ const rawValue = target.value;
236
+
237
+ // Strip out any non-digits
238
+ let digitsOnly = rawValue.replace(/[^\d]/g, "");
239
+
240
+ // Limit length based on card type
241
+ const maxLength = maxAllowedDigits.value;
242
+ digitsOnly = digitsOnly.slice(0, maxLength);
243
+
244
+ // Format the number
245
+ const { formatted, cursorPos } = formatCardNumber(digitsOnly);
246
+ internalValue.value = formatted;
247
+
248
+ // Update cursor position
249
+ cursorPosition.value = cursorPos;
250
+
251
+ // Emit events
252
+ emit("update:value", digitsOnly); // Raw digits
253
+ emit("update:formatted", formatted); // Formatted display
254
+ emit("update:cardType", cardInfo.value?.name || "");
255
+ emit("update:valid", isValid.value && isComplete.value);
256
+
257
+ // Set cursor position after Vue updates the DOM
258
+ requestAnimationFrame(() => {
259
+ if (inputRef.value && cursorPosition.value !== null) {
260
+ inputRef.value.setSelectionRange(
261
+ cursorPosition.value,
262
+ cursorPosition.value
263
+ );
264
+ }
265
+ });
266
+ };
267
+
268
+ // Handle paste
269
+ const handlePaste = (event: ClipboardEvent) => {
270
+ event.preventDefault();
271
+ const pastedText = event.clipboardData?.getData("text") || "";
272
+ let cleanedNumber = pastedText.replace(/\D/g, "");
273
+
274
+ if (cleanedNumber) {
275
+ // Detect card type first to know the limit
276
+ const cardType = detectCardType(cleanedNumber);
277
+ const maxLength = cardType
278
+ ? Math.max(...cardPatterns[cardType].lengths)
279
+ : 16;
280
+
281
+ // Enforce the limit
282
+ cleanedNumber = cleanedNumber.slice(0, maxLength);
283
+
284
+ const { formatted } = formatCardNumber(cleanedNumber);
285
+ internalValue.value = formatted;
286
+ emit("update:value", cleanedNumber);
287
+ emit("update:formatted", formatted);
288
+ emit("update:cardType", cardInfo.value?.name || "");
289
+ emit("update:valid", isValid.value && isComplete.value);
290
+ }
291
+ };
292
+
293
+ // Handle key down
294
+ const handleKeyDown = (event: KeyboardEvent) => {
295
+ const key = event.key;
296
+
297
+ // Block any non-digit keys except control keys
298
+ const isControlKey = [
299
+ "Backspace",
300
+ "Delete",
301
+ "Tab",
302
+ "Escape",
303
+ "Enter",
304
+ "ArrowLeft",
305
+ "ArrowRight",
306
+ "ArrowUp",
307
+ "ArrowDown",
308
+ ].includes(key);
309
+ const isModifierKey = event.ctrlKey || event.metaKey || event.altKey;
310
+
311
+ // If it's a digit and we're at max, prevent it
312
+ if (
313
+ /^[0-9]$/.test(key) &&
314
+ !isModifierKey &&
315
+ currentDigitCount.value >= maxAllowedDigits.value
316
+ ) {
317
+ event.preventDefault();
318
+ return;
319
+ }
320
+
321
+ if (!isControlKey && !isModifierKey && !/^[0-9]$/.test(key)) {
322
+ event.preventDefault();
323
+ }
324
+ };
325
+
326
+ // SVG icons for card types
327
+ const cardIcons = {
328
+ visa: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g class="nc-icon-wrapper"><rect x="2" y="7" width="28" height="18" rx="3" ry="3" fill="#1434cb" stroke-width="0"></rect><path d="m27,7H5c-1.657,0-3,1.343-3,3v12c0,1.657,1.343,3,3,3h22c1.657,0,3-1.343,3-3v-12c0-1.657-1.343-3-3-3Zm2,15c0,1.103-.897,2-2,2H5c-1.103,0-2-.897-2-2v-12c0-1.103.897-2,2-2h22c1.103,0,2,.897,2,2v12Z" stroke-width="0" opacity=".15"></path><path d="m27,8H5c-1.105,0-2,.895-2,2v1c0-1.105.895-2,2-2h22c1.105,0,2,.895,2,2v-1c0-1.105-.895-2-2-2Z" fill="#fff" opacity=".2" stroke-width="0"></path><path d="m13.392,12.624l-2.838,6.77h-1.851l-1.397-5.403c-.085-.332-.158-.454-.416-.595-.421-.229-1.117-.443-1.728-.576l.041-.196h2.98c.38,0,.721.253.808.69l.738,3.918,1.822-4.608h1.84Z" fill="#fff" stroke-width="0"></path><path d="m20.646,17.183c.008-1.787-2.47-1.886-2.453-2.684.005-.243.237-.501.743-.567.251-.032.943-.058,1.727.303l.307-1.436c-.421-.152-.964-.299-1.638-.299-1.732,0-2.95.92-2.959,2.238-.011.975.87,1.518,1.533,1.843.683.332.912.545.909.841-.005.454-.545.655-1.047.663-.881.014-1.392-.238-1.799-.428l-.318,1.484c.41.188,1.165.351,1.947.359,1.841,0,3.044-.909,3.05-2.317" fill="#fff" stroke-width="0"></path><path d="m25.423,12.624h-1.494c-.337,0-.62.195-.746.496l-2.628,6.274h1.839l.365-1.011h2.247l.212,1.011h1.62l-1.415-6.77Zm-2.16,4.372l.922-2.542.53,2.542h-1.452Z" fill="#fff" stroke-width="0"></path><path fill="#fff" stroke-width="0" d="M15.894 12.624L14.446 19.394 12.695 19.394 14.143 12.624 15.894 12.624z"></path></g></svg>`,
329
+ mastercard: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g class="nc-icon-wrapper"><rect x="2" y="7" width="28" height="18" rx="3" ry="3" fill="#141413" stroke-width="0"></rect><path d="m27,7H5c-1.657,0-3,1.343-3,3v12c0,1.657,1.343,3,3,3h22c1.657,0,3-1.343,3-3v-12c0-1.657-1.343-3-3-3Zm2,15c0,1.103-.897,2-2,2H5c-1.103,0-2-.897-2-2v-12c0-1.103.897-2,2-2h22c1.103,0,2,.897,2,2v12Z" stroke-width="0" opacity=".15"></path><path d="m27,8H5c-1.105,0-2,.895-2,2v1c0-1.105.895-2,2-2h22c1.105,0,2,.895,2,2v-1c0-1.105-.895-2-2-2Z" fill="#fff" opacity=".2" stroke-width="0"></path><path fill="#ff5f00" stroke-width="0" d="M13.597 11.677H18.407V20.32H13.597z"></path><path d="m13.902,15.999c0-1.68.779-3.283,2.092-4.322-2.382-1.878-5.849-1.466-7.727.932-1.863,2.382-1.451,5.833.947,7.712,2,1.573,4.795,1.573,6.795,0-1.329-1.038-2.107-2.642-2.107-4.322Z" fill="#eb001b" stroke-width="0"></path><path d="m24.897,15.999c0,3.039-2.459,5.497-5.497,5.497-1.237,0-2.428-.412-3.39-1.176,2.382-1.878,2.795-5.329.916-7.727-.275-.336-.58-.657-.916-.916,2.382-1.878,5.849-1.466,7.712.932.764.962,1.176,2.153,1.176,3.39Z" fill="#f79e1b" stroke-width="0"></path></g></svg>`,
330
+ amex: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g class="nc-icon-wrapper"><rect x="2" y="7" width="28" height="18" rx="3" ry="3" fill="#0f70ce" stroke-width="0"></rect><path d="m27.026,9l-.719,1.965-.708-1.965h-3.885v2.582l-1.136-2.582h-3.119l-3.259,7.409h2.637v6.591h8.097l1.316-1.458,1.322,1.458h2.244c.112-.314.184-.647.184-1v-1.041l-1.58-1.698,1.58-1.655v-7.606c0-.353-.072-.686-.184-1h-2.79Z" fill="#fff" stroke-width="0"></path><path d="m17.679,14.433h2.61l.502,1.148h1.78l-2.531-5.754h-2.039l-2.531,5.754h1.734l.477-1.148Zm1.307-3.135l.775,1.844h-1.535l.761-1.844Z" fill="#0f70ce" stroke-width="0"></path><path fill="#0f70ce" stroke-width="0" d="M22.542 9.827L25.018 9.827 26.302 13.39 27.604 9.827 30 9.827 30 15.581 28.45 15.581 28.45 11.603 26.977 15.581 25.608 15.581 24.124 11.631 24.124 15.581 22.542 15.581 22.542 9.827z"></path><path fill="#0f70ce" stroke-width="0" d="M19.24 20.82L19.24 19.944 22.484 19.944 22.484 18.624 19.24 18.624 19.24 17.748 22.565 17.748 22.565 16.409 17.664 16.409 17.664 22.173 22.565 22.173 22.565 20.82 19.24 20.82z"></path><path fill="#0f70ce" stroke-width="0" d="M24.638 16.409L26.271 18.234 27.968 16.409 30 16.409 27.283 19.254 30 22.173 27.939 22.173 26.249 20.309 24.567 22.173 22.537 22.173 25.272 19.275 22.537 16.409 24.638 16.409z"></path><path d="m27,7H5c-1.657,0-3,1.343-3,3v12c0,1.657,1.343,3,3,3h22c1.657,0,3-1.343,3-3v-12c0-1.657-1.343-3-3-3Zm2,15c0,1.103-.897,2-2,2H5c-1.103,0-2-.897-2-2v-12c0-1.103.897-2,2-2h22c1.103,0,2,.897,2,2v12Z" stroke-width="0" opacity=".15"></path><path d="m27,8H5c-1.105,0-2,.895-2,2v1c0-1.105.895-2,2-2h22c1.105,0,2,.895,2,2v-1c0-1.105-.895-2-2-2Z" fill="#fff" opacity=".2" stroke-width="0"></path></g></svg>`,
331
+ discover: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><g class="nc-icon-wrapper"><rect x="2" y="7" width="28" height="18" rx="3" ry="3" fill="#fff" stroke-width="0"></rect><path d="m27,7h-8c4.971,0,9,4.029,9,9s-4.029,9-9,9h8c1.657,0,3-1.343,3-3v-12c0-1.657-1.343-3-3-3Z" fill="#f47922" stroke-width="0"></path><path d="m27,7H5c-1.657,0-3,1.343-3,3v12c0,1.657,1.343,3,3,3h22c1.657,0,3-1.343,3-3v-12c0-1.657-1.343-3-3-3Zm2,15c0,1.103-.897,2-2,2H5c-1.103,0-2-.897-2-2v-12c0-1.103.897-2,2-2h22c1.103,0,2,.897,2,2v12Z" stroke-width="0" opacity=".15"></path><path d="m27,8H5c-1.105,0-2,.895-2,2v1c0-1.105.895-2,2-2h22c1.105,0,2,.895,2,2v-1c0-1.105-.895-2-2-2Z" fill="#fff" opacity=".2" stroke-width="0"></path><path d="m5.081,14.116h-1.081v3.777h1.076c.572,0,.985-.135,1.348-.436.431-.357.686-.894.686-1.45,0-1.115-.833-1.891-2.027-1.891Zm.86,2.837c-.231.209-.532.3-1.008.3h-.198v-2.497h.198c.476,0,.765.085,1.008.305.255.227.408.578.408.94s-.153.725-.408.952Z" fill="#231f20" stroke-width="0"></path><path fill="#231f20" stroke-width="0" d="M7.448 14.116H8.185V17.893H7.448z"></path><path d="m9.986,15.565c-.442-.164-.572-.271-.572-.475,0-.238.231-.419.549-.419.221,0,.402.091.594.306l.386-.505c-.317-.277-.696-.419-1.11-.419-.668,0-1.178.464-1.178,1.082,0,.52.237.787.929,1.036.288.102.435.17.509.215.147.096.221.232.221.391,0,.306-.243.533-.572.533-.351,0-.634-.175-.804-.504l-.476.458c.339.498.747.719,1.308.719.766,0,1.303-.509,1.303-1.24,0-.6-.248-.872-1.086-1.178Z" fill="#231f20" stroke-width="0"></path><path d="m11.305,16.007c0,1.11.872,1.971,1.994,1.971.317,0,.589-.062.924-.22v-.867c-.295.295-.555.414-.889.414-.742,0-1.269-.538-1.269-1.303,0-.725.543-1.297,1.234-1.297.351,0,.617.125.924.425v-.867c-.323-.164-.589-.232-.906-.232-1.116,0-2.011.878-2.011,1.976Z" fill="#231f20" stroke-width="0"></path><path fill="#231f20" stroke-width="0" d="M20.063 16.653L19.056 14.116 18.251 14.116 19.854 17.99 20.25 17.99 21.882 14.116 21.083 14.116 20.063 16.653z"></path><path fill="#231f20" stroke-width="0" d="M22.215 17.893L24.304 17.893 24.304 17.253 22.951 17.253 22.951 16.234 24.254 16.234 24.254 15.594 22.951 15.594 22.951 14.756 24.304 14.756 24.304 14.116 22.215 14.116 22.215 17.893z"></path><path d="m27.221,15.231c0-.707-.487-1.115-1.337-1.115h-1.092v3.777h.736v-1.517h.096l1.02,1.517h.906l-1.189-1.591c.555-.113.861-.492.861-1.071Zm-1.478.624h-.216v-1.144h.227c.459,0,.708.192.708.56,0,.38-.249.584-.72.584Z" fill="#231f20" stroke-width="0"></path><path d="m18.461,16c0,1.105-.895,2-2,2s-2-.895-2-2,.895-2,2-2,2,.895,2,2Z" fill="#f47922" stroke-width="0"></path></g></svg>`,
332
+ };
333
+
334
+ // Get card icon
335
+ const getCardIcon = computed(() => {
336
+ if (!cardType.value || !props.showCardType) return null;
337
+ return cardIcons[cardType.value as keyof typeof cardIcons] || null;
338
+ });
339
+
340
+ // Get the effective icon color based on state
341
+ const getEffectiveIconColor = () => {
342
+ return "var(--input-text-filled)";
343
+ };
344
+
345
+ const getPlaceholder = computed(() => {
346
+ if (props.state === "readonly") {
347
+ return "Field Cannot Be Edited";
348
+ }
349
+ return props.placeholder;
350
+ });
351
+ </script>
352
+
353
+ <template>
354
+ <div :class="$style.container">
355
+ <div :class="[$style.input_container, $style[effectiveState]]">
356
+ <input
357
+ ref="inputRef"
358
+ class="body"
359
+ type="text"
360
+ inputmode="numeric"
361
+ autocomplete="cc-number"
362
+ :placeholder="getPlaceholder"
363
+ :value="internalValue"
364
+ @beforeinput="handleBeforeInput"
365
+ @input="handleInput"
366
+ @paste="handlePaste"
367
+ @keydown="handleKeyDown"
368
+ :maxlength="24"
369
+ :disabled="state === 'disabled'"
370
+ :readonly="state === 'readonly'"
371
+ />
372
+ <transition name="fade">
373
+ <div
374
+ v-if="cardType && showCardType"
375
+ :class="$style.card_icon"
376
+ v-html="getCardIcon"
377
+ ></div>
378
+ </transition>
379
+ <transition name="fade">
380
+ <div v-if="isComplete && !isValid" :class="$style.error_icon">
381
+ <TriangleWarningIcon size="16" />
382
+ </div>
383
+ </transition>
384
+ <CreditCardIcon v-if="!internalValue" :size="16" />
385
+ </div>
386
+ <transition name="slide-fade">
387
+ <p
388
+ v-if="isComplete && !isValid"
389
+ :class="[$style.error_message, 'footnote']"
390
+ >
391
+ Please enter a valid card number
392
+ </p>
393
+ </transition>
394
+ </div>
395
+ </template>
396
+
397
+ <style module>
398
+ .container {
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: 0.588rem;
402
+ }
403
+
404
+ .input_container {
405
+ width: 100%;
406
+ padding-left: 0.882rem;
407
+ padding-right: 0.882rem;
408
+ padding-top: 0.588rem;
409
+ padding-bottom: 0.588rem;
410
+ border: var(--input-border);
411
+ border-radius: var(--input-border-radius);
412
+ transition: background-color 0.3s ease, border 0.3s ease;
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: space-between;
416
+ position: relative;
417
+ }
418
+
419
+ .input_container:focus-within {
420
+ border: 1px solid var(--input-focus-border);
421
+ }
422
+
423
+ .input_container input {
424
+ border: none;
425
+ background: transparent;
426
+ outline: none;
427
+ width: 100%;
428
+ font-family: inherit;
429
+ letter-spacing: 0.02em;
430
+ }
431
+
432
+ /* State-based styling using CSS variables */
433
+ .input_container.normal:has(input:placeholder-shown),
434
+ .input_container.active:has(input:placeholder-shown) {
435
+ background-color: var(--input-background-normal);
436
+ color: var(--input-text-empty);
437
+ }
438
+
439
+ .input_container.normal:has(input:not(:placeholder-shown)),
440
+ .input_container.active:has(input:not(:placeholder-shown)) {
441
+ background-color: var(--input-background-filled);
442
+ color: var(--input-text-filled);
443
+ border-color: var(--input-border-filled);
444
+ }
445
+
446
+ .input_container.normal input:placeholder-shown,
447
+ .input_container.active input:placeholder-shown {
448
+ color: var(--input-text-empty);
449
+ opacity: 0.6;
450
+ }
451
+
452
+ .input_container.normal input:not(:placeholder-shown),
453
+ .input_container.active input:not(:placeholder-shown) {
454
+ color: var(--input-text-filled);
455
+ }
456
+
457
+ .input_container.disabled:has(input:placeholder-shown),
458
+ .input_container.disabled:has(input:not(:placeholder-shown)) {
459
+ background-color: var(--input-disabled-bg);
460
+ color: var(--input-disabled-text);
461
+ border-color: var(--input-disabled-border);
462
+ cursor: not-allowed;
463
+ }
464
+
465
+ .input_container.disabled input:placeholder-shown,
466
+ .input_container.disabled input:not(:placeholder-shown) {
467
+ color: var(--input-disabled-text);
468
+ cursor: not-allowed;
469
+ }
470
+
471
+ .input_container.readonly:has(input:placeholder-shown),
472
+ .input_container.readonly:has(input:not(:placeholder-shown)) {
473
+ background-color: var(--input-readonly-bg);
474
+ color: var(--input-readonly-text);
475
+ border-color: var(--input-readonly-border);
476
+ cursor: not-allowed;
477
+ }
478
+
479
+ .input_container.readonly input:placeholder-shown,
480
+ .input_container.readonly input:not(:placeholder-shown) {
481
+ color: var(--input-readonly-text);
482
+ cursor: not-allowed;
483
+ }
484
+
485
+ .input_container.error:has(input:placeholder-shown),
486
+ .input_container.error:has(input:not(:placeholder-shown)) {
487
+ background-color: var(--input-error-bg);
488
+ color: var(--input-error-text);
489
+ border-color: var(--input-error-border);
490
+ }
491
+
492
+ .input_container.error input:placeholder-shown,
493
+ .input_container.error input:not(:placeholder-shown) {
494
+ color: var(--input-error-text);
495
+ }
496
+
497
+ .input_container.active {
498
+ pointer-events: none;
499
+ }
500
+
501
+ .card_icon {
502
+ max-height: 1.5rem;
503
+ display: flex;
504
+ align-items: center;
505
+ justify-content: center;
506
+ overflow: hidden;
507
+ }
508
+
509
+ .card_icon svg {
510
+ width: 100%;
511
+ height: 100%;
512
+ object-fit: contain;
513
+ }
514
+
515
+ .error_icon {
516
+ color: var(--input-text-error);
517
+ display: flex;
518
+ align-items: center;
519
+ animation: shake 0.3s ease-in-out;
520
+ }
521
+
522
+ /* Show both card icon and error icon when needed */
523
+ .input_container:has(.card_icon) .error_icon {
524
+ right: 4rem;
525
+ }
526
+
527
+ .error_message {
528
+ color: var(--input-text-error);
529
+ }
530
+
531
+ /* Animations */
532
+ @keyframes shake {
533
+ 0%,
534
+ 100% {
535
+ transform: translateX(0);
536
+ }
537
+ 25% {
538
+ transform: translateX(-5px);
539
+ }
540
+ 75% {
541
+ transform: translateX(5px);
542
+ }
543
+ }
544
+
545
+ .fade-enter-active,
546
+ .fade-leave-active {
547
+ transition: opacity 0.3s ease;
548
+ }
549
+
550
+ .fade-enter-from,
551
+ .fade-leave-to {
552
+ opacity: 0;
553
+ }
554
+
555
+ .slide-fade-enter-active {
556
+ transition: all 0.3s ease;
557
+ }
558
+
559
+ .slide-fade-leave-active {
560
+ transition: all 0.2s ease;
561
+ }
562
+
563
+ .slide-fade-enter-from {
564
+ transform: translateY(-10px);
565
+ opacity: 0;
566
+ }
567
+
568
+ .slide-fade-leave-to {
569
+ transform: translateY(-10px);
570
+ opacity: 0;
571
+ }
572
+
573
+ .input_container input::placeholder {
574
+ color: var(--input-placeholder);
575
+ }
576
+
577
+ .input_container:has(input:not(:placeholder-shown)) input::placeholder {
578
+ color: var(--input-placeholder-filled);
579
+ }
580
+
581
+ .input_container.readonly input::placeholder {
582
+ color: var(--input-readonly-placeholder);
583
+ }
584
+
585
+ .input_container.disabled input::placeholder {
586
+ color: var(--input-disabled-placeholder);
587
+ }
588
+
589
+ /* Responsive adjustments */
590
+ @media (max-width: 480px) {
591
+ .input_container input {
592
+ font-size: 16px; /* Prevents zoom on iOS */
593
+ }
594
+
595
+ .card_icon {
596
+ width: 40px;
597
+ height: 26px;
598
+ }
599
+ }
600
+ </style>