@ttdk-design-system/library-react 0.0.0-stage → 1.0.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 (184) hide show
  1. package/AI_GUIDE.md +6518 -0
  2. package/llms.txt +6518 -0
  3. package/package.json +1098 -4
  4. package/src/core/index.ts +2 -0
  5. package/src/core/tailwind-preset.cjs +62 -0
  6. package/src/core/tailwind-preset.ts +85 -0
  7. package/src/core/theme/tokens.css +663 -0
  8. package/src/core/types.ts +836 -0
  9. package/src/index.ts +17 -0
  10. package/src/tailwind-preset.cjs +62 -0
  11. package/src/tailwind-preset.ts +85 -0
  12. package/src/types.ts +1 -0
  13. package/src/web/accordion/AI_GUIDE.md +149 -0
  14. package/src/web/accordion/accordion.css +1 -0
  15. package/src/web/accordion/accordion.tsx +430 -0
  16. package/src/web/accordion/index.ts +3 -0
  17. package/src/web/accordion/manifest.json +351 -0
  18. package/src/web/alert/AI_GUIDE.md +139 -0
  19. package/src/web/alert/alert.css +1 -0
  20. package/src/web/alert/alert.tsx +318 -0
  21. package/src/web/alert/index.ts +3 -0
  22. package/src/web/alert/manifest.json +240 -0
  23. package/src/web/avatar/AI_GUIDE.md +118 -0
  24. package/src/web/avatar/avatar.css +1 -0
  25. package/src/web/avatar/avatar.tsx +233 -0
  26. package/src/web/avatar/index.ts +3 -0
  27. package/src/web/avatar/manifest.json +195 -0
  28. package/src/web/badge/AI_GUIDE.md +94 -0
  29. package/src/web/badge/badge.css +1 -0
  30. package/src/web/badge/badge.tsx +140 -0
  31. package/src/web/badge/index.ts +2 -0
  32. package/src/web/badge/manifest.json +141 -0
  33. package/src/web/banner/AI_GUIDE.md +130 -0
  34. package/src/web/banner/banner.css +1 -0
  35. package/src/web/banner/banner.tsx +334 -0
  36. package/src/web/banner/index.ts +3 -0
  37. package/src/web/banner/manifest.json +240 -0
  38. package/src/web/breadcrumb/AI_GUIDE.md +120 -0
  39. package/src/web/breadcrumb/breadcrumb.css +1 -0
  40. package/src/web/breadcrumb/breadcrumb.tsx +306 -0
  41. package/src/web/breadcrumb/index.ts +3 -0
  42. package/src/web/breadcrumb/manifest.json +225 -0
  43. package/src/web/button/AI_GUIDE.md +121 -0
  44. package/src/web/button/button.css +1 -0
  45. package/src/web/button/button.tsx +287 -0
  46. package/src/web/button/index.ts +2 -0
  47. package/src/web/button/manifest.json +177 -0
  48. package/src/web/calendar/AI_GUIDE.md +151 -0
  49. package/src/web/calendar/calendar.css +1 -0
  50. package/src/web/calendar/calendar.tsx +1611 -0
  51. package/src/web/calendar/index.ts +3 -0
  52. package/src/web/calendar/manifest.json +470 -0
  53. package/src/web/card/AI_GUIDE.md +133 -0
  54. package/src/web/card/card.css +1 -0
  55. package/src/web/card/card.tsx +408 -0
  56. package/src/web/card/index.ts +4 -0
  57. package/src/web/card/manifest.json +357 -0
  58. package/src/web/checkbox/AI_GUIDE.md +146 -0
  59. package/src/web/checkbox/checkbox.css +1 -0
  60. package/src/web/checkbox/checkbox.tsx +307 -0
  61. package/src/web/checkbox/index.ts +2 -0
  62. package/src/web/checkbox/manifest.json +294 -0
  63. package/src/web/chip/AI_GUIDE.md +186 -0
  64. package/src/web/chip/chip.css +1 -0
  65. package/src/web/chip/chip.tsx +230 -0
  66. package/src/web/chip/index.ts +2 -0
  67. package/src/web/chip/manifest.json +186 -0
  68. package/src/web/dropdown/AI_GUIDE.md +167 -0
  69. package/src/web/dropdown/dropdown.css +1 -0
  70. package/src/web/dropdown/dropdown.tsx +1274 -0
  71. package/src/web/dropdown/index.ts +3 -0
  72. package/src/web/dropdown/manifest.json +576 -0
  73. package/src/web/flex/AI_GUIDE.md +113 -0
  74. package/src/web/flex/flex-item.tsx +97 -0
  75. package/src/web/flex/flex.css +1 -0
  76. package/src/web/flex/flex.tsx +108 -0
  77. package/src/web/flex/index.ts +5 -0
  78. package/src/web/flex/manifest.json +162 -0
  79. package/src/web/form/index.ts +7 -0
  80. package/src/web/grid/AI_GUIDE.md +112 -0
  81. package/src/web/grid/grid-item.tsx +112 -0
  82. package/src/web/grid/grid.css +1 -0
  83. package/src/web/grid/grid.tsx +99 -0
  84. package/src/web/grid/index.ts +5 -0
  85. package/src/web/grid/manifest.json +153 -0
  86. package/src/web/index.ts +86 -0
  87. package/src/web/input/AI_GUIDE.md +159 -0
  88. package/src/web/input/index.ts +3 -0
  89. package/src/web/input/input.css +1 -0
  90. package/src/web/input/input.tsx +537 -0
  91. package/src/web/input/manifest.json +465 -0
  92. package/src/web/input-number/AI_GUIDE.md +164 -0
  93. package/src/web/input-number/index.ts +3 -0
  94. package/src/web/input-number/input-number.css +1 -0
  95. package/src/web/input-number/input-number.tsx +1193 -0
  96. package/src/web/input-number/manifest.json +575 -0
  97. package/src/web/modal/AI_GUIDE.md +171 -0
  98. package/src/web/modal/index.ts +3 -0
  99. package/src/web/modal/manifest.json +375 -0
  100. package/src/web/modal/modal.css +1 -0
  101. package/src/web/modal/modal.tsx +571 -0
  102. package/src/web/pagination/AI_GUIDE.md +148 -0
  103. package/src/web/pagination/index.ts +21 -0
  104. package/src/web/pagination/manifest.json +432 -0
  105. package/src/web/pagination/pagination-content.tsx +45 -0
  106. package/src/web/pagination/pagination-ellipsis.tsx +54 -0
  107. package/src/web/pagination/pagination-first.tsx +155 -0
  108. package/src/web/pagination/pagination-item.tsx +45 -0
  109. package/src/web/pagination/pagination-last.tsx +155 -0
  110. package/src/web/pagination/pagination-link.tsx +121 -0
  111. package/src/web/pagination/pagination-next.tsx +158 -0
  112. package/src/web/pagination/pagination-previous.tsx +158 -0
  113. package/src/web/pagination/pagination.css +1 -0
  114. package/src/web/pagination/pagination.tsx +687 -0
  115. package/src/web/popover/AI_GUIDE.md +163 -0
  116. package/src/web/popover/index.ts +3 -0
  117. package/src/web/popover/manifest.json +396 -0
  118. package/src/web/popover/popover.css +1 -0
  119. package/src/web/popover/popover.tsx +702 -0
  120. package/src/web/radio/AI_GUIDE.md +155 -0
  121. package/src/web/radio/index.ts +2 -0
  122. package/src/web/radio/manifest.json +294 -0
  123. package/src/web/radio/radio.css +1 -0
  124. package/src/web/radio/radio.tsx +290 -0
  125. package/src/web/selectbox/AI_GUIDE.md +216 -0
  126. package/src/web/selectbox/index.ts +3 -0
  127. package/src/web/selectbox/manifest.json +477 -0
  128. package/src/web/selectbox/selectbox.css +1 -0
  129. package/src/web/selectbox/selectbox.tsx +1048 -0
  130. package/src/web/sonner/AI_GUIDE.md +98 -0
  131. package/src/web/sonner/index.ts +114 -0
  132. package/src/web/sonner/manifest.json +132 -0
  133. package/src/web/sonner/sonner.css +1 -0
  134. package/src/web/sonner/sonner.tsx +177 -0
  135. package/src/web/table/AI_GUIDE.md +145 -0
  136. package/src/web/table/index.ts +35 -0
  137. package/src/web/table/manifest.json +168 -0
  138. package/src/web/table/table-body.tsx +45 -0
  139. package/src/web/table/table-caption.tsx +45 -0
  140. package/src/web/table/table-cell.tsx +74 -0
  141. package/src/web/table/table-empty.tsx +108 -0
  142. package/src/web/table/table-footer.tsx +45 -0
  143. package/src/web/table/table-head.tsx +80 -0
  144. package/src/web/table/table-header.tsx +45 -0
  145. package/src/web/table/table-row.tsx +69 -0
  146. package/src/web/table/table.css +1 -0
  147. package/src/web/table/table.tsx +305 -0
  148. package/src/web/tabs/AI_GUIDE.md +150 -0
  149. package/src/web/tabs/index.ts +3 -0
  150. package/src/web/tabs/manifest.json +315 -0
  151. package/src/web/tabs/tabs.css +1 -0
  152. package/src/web/tabs/tabs.tsx +332 -0
  153. package/src/web/text/AI_GUIDE.md +129 -0
  154. package/src/web/text/index.ts +3 -0
  155. package/src/web/text/manifest.json +195 -0
  156. package/src/web/text/text.css +1 -0
  157. package/src/web/text/text.tsx +689 -0
  158. package/src/web/textarea/AI_GUIDE.md +141 -0
  159. package/src/web/textarea/index.ts +3 -0
  160. package/src/web/textarea/manifest.json +438 -0
  161. package/src/web/textarea/textarea.css +1 -0
  162. package/src/web/textarea/textarea.tsx +467 -0
  163. package/src/web/tooltip/AI_GUIDE.md +110 -0
  164. package/src/web/tooltip/index.ts +3 -0
  165. package/src/web/tooltip/manifest.json +222 -0
  166. package/src/web/tooltip/tooltip.css +1 -0
  167. package/src/web/tooltip/tooltip.tsx +423 -0
  168. package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
  169. package/src/webview/bottomsheet/bottomsheet.css +1 -0
  170. package/src/webview/bottomsheet/bottomsheet.tsx +603 -0
  171. package/src/webview/bottomsheet/index.ts +4 -0
  172. package/src/webview/bottomsheet/manifest.json +377 -0
  173. package/src/webview/button/AI_GUIDE.md +79 -0
  174. package/src/webview/button/button-ios.css +1 -0
  175. package/src/webview/button/button-ios.tsx +138 -0
  176. package/src/webview/button/button-material.css +1 -0
  177. package/src/webview/button/button-material.tsx +138 -0
  178. package/src/webview/button/index.ts +7 -0
  179. package/src/webview/button/manifest.json +148 -0
  180. package/src/webview/index.ts +7 -0
  181. package/tailwind-preset.cjs +62 -0
  182. package/tailwind-preset.js +62 -0
  183. package/ttdk-manifest.json +10188 -0
  184. package/README.md +0 -3
@@ -0,0 +1,836 @@
1
+ import type * as CSS from 'csstype';
2
+
3
+ /**
4
+ * TTDK Design System - Responsive Breakpoints Type System
5
+ *
6
+ * Standard Breakpoint Scales:
7
+ * - `sm`: min-width 640px
8
+ * - `lt_sm`: max-width 639px (less than sm)
9
+ * - `md`: min-width 768px
10
+ * - `lt_md`: max-width 767px (less than md)
11
+ * - `lg`: min-width 1024px
12
+ * - `lt_lg`: max-width 1023px (less than lg)
13
+ * - `xl`: min-width 1280px
14
+ * - `lt_xl`: max-width 1279px (less than xl)
15
+ */
16
+ export interface ResponsiveBreakpoints<T> {
17
+ sm?: Partial<T>;
18
+ lt_sm?: Partial<T>;
19
+ md?: Partial<T>;
20
+ lt_md?: Partial<T>;
21
+ lg?: Partial<T>;
22
+ lt_lg?: Partial<T>;
23
+ xl?: Partial<T>;
24
+ lt_xl?: Partial<T>;
25
+ }
26
+ export const ResponsiveBreakpoints = {};
27
+ export const UiProps = {};
28
+
29
+ /**
30
+ * Type helper to allow flat props (default) combined with nested responsive breakpoint overrides.
31
+ */
32
+ export type ResponsiveUiProps<T> = T & ResponsiveBreakpoints<T>;
33
+
34
+ /**
35
+ * Helper to build responsive CSS custom properties from a ResponsiveUiProps object.
36
+ */
37
+ export function buildResponsiveVars(
38
+ ui: any,
39
+ keyMap: Record<string, string>
40
+ ): any {
41
+ if (!ui) return {};
42
+ const styles: Record<string, string> = {};
43
+ const breakpoints = ['sm', 'lt_sm', 'md', 'lt_md', 'lg', 'lt_lg', 'xl', 'lt_xl'] as const;
44
+
45
+ // Process default / base level
46
+ for (const [uiKey, cssVar] of Object.entries(keyMap)) {
47
+ if (ui[uiKey] !== undefined) {
48
+ styles[cssVar] = String(ui[uiKey]);
49
+ }
50
+ }
51
+
52
+ // Process each breakpoint
53
+ for (const bp of breakpoints) {
54
+ const bpObj = ui[bp];
55
+ if (bpObj && typeof bpObj === 'object') {
56
+ for (const [uiKey, cssVar] of Object.entries(keyMap)) {
57
+ if (bpObj[uiKey] !== undefined) {
58
+ const bpSuffix = bp.replace('_', '-');
59
+ styles[`${cssVar}-${bpSuffix}`] = String(bpObj[uiKey]);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ return styles;
66
+ }
67
+
68
+ // ============================================================
69
+ // NEW: Generic UI Props — style-like object + pseudo support
70
+ // ============================================================
71
+
72
+ /** CSS properties yang bisa di-set via ui prop */
73
+ export type CSSValue = string | number;
74
+
75
+ /** Pseudo-class props yang support di ui{} */
76
+ export type UiPseudoProps = CSS.Properties<string | number> & {
77
+ // Aliases & shortcuts
78
+ bg?: CSSValue;
79
+ size?: CSSValue;
80
+ weight?: CSSValue;
81
+
82
+ // Escape hatch — property CSS custom variable apapun
83
+ [cssProperty: string]: any;
84
+ };
85
+
86
+ /**
87
+ * Pseudo-class props yang juga mendukung nested responsive breakpoints:
88
+ * hover: { background: 'salmon', md: { background: 'teal' } }
89
+ */
90
+ export type UiPseudoStateProps = UiPseudoProps & {
91
+ sm?: UiPseudoProps;
92
+ md?: UiPseudoProps;
93
+ lg?: UiPseudoProps;
94
+ xl?: UiPseudoProps;
95
+ };
96
+
97
+ /**
98
+ * Breakpoint props yang mendukung flat CSS properties plus nested pseudo-classes:
99
+ * md: { background: 'teal', hover: { background: '#fae8ff', borderColor: '#a21caf' }, focus: { borderColor: '#c026d3' } }
100
+ */
101
+ export type UiBreakpointProps = UiPseudoProps & {
102
+ /** Style saat :hover pada breakpoint ini */
103
+ hover?: UiPseudoProps;
104
+ /** Style saat :active pada breakpoint ini */
105
+ active?: UiPseudoProps;
106
+ /** Style saat :focus / :focus-visible pada breakpoint ini */
107
+ focus?: UiPseudoProps;
108
+ /** Style saat disabled pada breakpoint ini */
109
+ disabled?: UiPseudoProps;
110
+ /** Style saat dark mode aktif (.dark ancestor class) pada breakpoint ini */
111
+ dark?: UiPseudoProps;
112
+ };
113
+
114
+ /**
115
+ * UiProps — seperti style object CSS biasa, tapi dengan support pseudo-class & responsive breakpoints.
116
+ *
117
+ * Mendukung SEMUA CSS property secara otomatis dengan autocomplete via csstype.
118
+ *
119
+ * @example
120
+ * ```tsx
121
+ * <Button
122
+ * ui={{
123
+ * background: 'purple',
124
+ * color: 'white',
125
+ * borderRadius: '99px',
126
+ * backdropFilter: 'blur(8px)',
127
+ * hover: {
128
+ * background: 'darkpurple',
129
+ * transform: 'scale(1.02)',
130
+ * },
131
+ * active: { opacity: '0.85' },
132
+ * // Dark mode via .dark ancestor class (kompatibel Tailwind dark mode)
133
+ * dark: {
134
+ * background: '#1a1a2e',
135
+ * color: '#e2e8f0',
136
+ * },
137
+ * }}
138
+ * />
139
+ * ```
140
+ */
141
+ export type UiProps = UiPseudoProps & {
142
+ // Pseudo-class support
143
+ /** Style saat :hover */
144
+ hover?: UiPseudoStateProps;
145
+ /** Style saat :active */
146
+ active?: UiPseudoStateProps;
147
+ /** Style saat :focus / :focus-visible */
148
+ focus?: UiPseudoStateProps;
149
+ /** Style saat disabled */
150
+ disabled?: UiPseudoStateProps;
151
+ /**
152
+ * Style saat dark mode aktif — diterapkan lewat ancestor class `.dark`.
153
+ * Kompatibel dengan Tailwind CSS dark mode (class strategy).
154
+ *
155
+ * @example
156
+ * ```tsx
157
+ * ui={{ color: '#141313', dark: { color: '#e2e8f0', background: '#1a1a2e' } }}
158
+ * ```
159
+ * Menghasilkan: `.dark [style*="--ttdk-ui-dark-color:"] { color: var(...) !important; }`
160
+ */
161
+ dark?: UiPseudoStateProps;
162
+
163
+ // Responsive Breakpoint support (mobile-first min-width):
164
+ /** Overrides saat min-width: 640px */
165
+ sm?: UiBreakpointProps;
166
+ /** Overrides saat min-width: 768px */
167
+ md?: UiBreakpointProps;
168
+ /** Overrides saat min-width: 1024px */
169
+ lg?: UiBreakpointProps;
170
+ /** Overrides saat min-width: 1280px */
171
+ xl?: UiBreakpointProps;
172
+ /** Overrides saat min-width: 1536px */
173
+ '2xl'?: UiBreakpointProps;
174
+
175
+ // Escape hatch — property CSS atau custom variable apapun
176
+ [key: string]: any;
177
+ };
178
+
179
+ const PSEUDOS = ['hover', 'active', 'focus', 'disabled', 'dark'] as const;
180
+ const BREAKPOINTS = ['sm', 'lt_sm', 'lt-sm', 'md', 'lt_md', 'lt-md', 'lg', 'lt_lg', 'lt-lg', 'xl', 'lt_xl', 'lt-xl', '2xl'] as const;
181
+
182
+ const BREAKPOINT_QUERIES: Record<string, string> = {
183
+ sm: '(min-width: 640px)',
184
+ lt_sm: '(max-width: 639px)',
185
+ 'lt-sm': '(max-width: 639px)',
186
+ md: '(min-width: 768px)',
187
+ lt_md: '(max-width: 767px)',
188
+ 'lt-md': '(max-width: 767px)',
189
+ lg: '(min-width: 1024px)',
190
+ lt_lg: '(max-width: 1023px)',
191
+ 'lt-lg': '(max-width: 1023px)',
192
+ xl: '(min-width: 1280px)',
193
+ lt_xl: '(max-width: 1279px)',
194
+ 'lt-xl': '(max-width: 1279px)',
195
+ '2xl': '(min-width: 1536px)',
196
+ };
197
+
198
+ const BUCKET_ORDER = [
199
+ 'pseudos',
200
+ 'lt_xl',
201
+ 'lt-xl',
202
+ 'lt_lg',
203
+ 'lt-lg',
204
+ 'lt_md',
205
+ 'lt-md',
206
+ 'lt_sm',
207
+ 'lt-sm',
208
+ 'sm',
209
+ 'md',
210
+ 'lg',
211
+ 'xl',
212
+ '2xl',
213
+ ] as const;
214
+
215
+ /** Convert camelCase ke kebab-case: backgroundColor → background-color */
216
+ function toKebab(str: string): string {
217
+ return str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
218
+ }
219
+
220
+ /** Membersihkan akhiran !important jika user tidak sengaja menyertakannya di string nilai */
221
+ export function cleanUiValue(v: any): string {
222
+ if (typeof v === 'string') {
223
+ return v.replace(/\s*!important\s*$/i, '').trim();
224
+ }
225
+ return String(v);
226
+ }
227
+
228
+ function resolveCssProperty(key: string): string {
229
+ if (key === 'bg') return 'background-color';
230
+ if (key === 'size') return 'font-size';
231
+ if (key === 'weight') return 'font-weight';
232
+ return toKebab(key);
233
+ }
234
+
235
+ // ============================================================
236
+ // Dynamic Runtime CSS Injection Engine
237
+ // ============================================================
238
+
239
+ const injectedRuleSet = new Set<string>();
240
+ const styleBuckets: Record<string, HTMLStyleElement> = {};
241
+
242
+ function getBucketElement(bucket: string): HTMLStyleElement | null {
243
+ if (typeof document === 'undefined') return null;
244
+ if (styleBuckets[bucket] && document.head.contains(styleBuckets[bucket])) {
245
+ return styleBuckets[bucket];
246
+ }
247
+
248
+ const safeId = `ttdk-dyn-${bucket.replace('_', '-')}`;
249
+ let el = document.getElementById(safeId) as HTMLStyleElement | null;
250
+ if (!el) {
251
+ el = document.createElement('style');
252
+ el.id = safeId;
253
+ el.setAttribute('data-ttdk-dynamic', 'true');
254
+
255
+ // Pastikan urutan tag style di <head> sesuai cascade specificity
256
+ const bucketIndex = BUCKET_ORDER.indexOf(bucket as any);
257
+ let nextSibling: HTMLElement | null = null;
258
+ if (bucketIndex !== -1) {
259
+ for (let i = bucketIndex + 1; i < BUCKET_ORDER.length; i++) {
260
+ const nextId = `ttdk-dyn-${BUCKET_ORDER[i].replace('_', '-')}`;
261
+ const nextEl = document.getElementById(nextId);
262
+ if (nextEl) {
263
+ nextSibling = nextEl;
264
+ break;
265
+ }
266
+ }
267
+ }
268
+
269
+ if (nextSibling) {
270
+ document.head.insertBefore(el, nextSibling);
271
+ } else {
272
+ document.head.appendChild(el);
273
+ }
274
+ }
275
+
276
+ styleBuckets[bucket] = el;
277
+ return el;
278
+ }
279
+
280
+ export function injectDynamicCss(bucket: string, ruleKey: string, cssRule: string) {
281
+ if (injectedRuleSet.has(ruleKey)) return;
282
+ injectedRuleSet.add(ruleKey);
283
+
284
+ const el = getBucketElement(bucket);
285
+ if (!el) return;
286
+
287
+ el.appendChild(document.createTextNode(cssRule + '\n'));
288
+ }
289
+
290
+ function getBaseVarRule(pseudo: string, varName: string, cssProp: string): string {
291
+ const baseVar = varName.replace(`-${pseudo}-`, '-');
292
+ if (baseVar === varName) return '';
293
+ let extra = `${baseVar}: var(${varName}) !important;`;
294
+ // Extra aliases for background
295
+ if (baseVar.endsWith('-background') || baseVar.endsWith('-bg')) {
296
+ extra += ` ${baseVar.replace(/-(background|bg)$/, '-bg')}: var(${varName}) !important;`;
297
+ extra += ` ${baseVar.replace(/-(background|bg)$/, '-background')}: var(${varName}) !important;`;
298
+ extra += ` ${baseVar.replace(/-(background|bg)$/, '-background-color')}: var(${varName}) !important;`;
299
+ }
300
+ return extra;
301
+ }
302
+
303
+ function injectPseudoRule(pseudo: string, varName: string, cssProp: string) {
304
+ const baseVarRule = getBaseVarRule(pseudo, varName, cssProp);
305
+
306
+ // Dark mode menggunakan ancestor class selector, bukan CSS pseudo-class
307
+ if (pseudo === 'dark') {
308
+ const selector = `.dark [style*="${varName}:"], .dark[style*="${varName}:"], [data-theme="dark"] [style*="${varName}:"], [data-theme="dark"][style*="${varName}:"]`;
309
+ const rule = `${selector} { ${cssProp}: var(${varName}) !important; ${baseVarRule} }`;
310
+ injectDynamicCss('pseudos', `pseudo:${varName}:${cssProp}`, rule);
311
+ return;
312
+ }
313
+
314
+ let pseudoSelector = `:hover`;
315
+ if (pseudo === 'active') pseudoSelector = `:active`;
316
+ else if (pseudo === 'focus') pseudoSelector = `:focus, [style*="${varName}:"]:focus-visible`;
317
+ else if (pseudo === 'disabled') pseudoSelector = `:disabled, [style*="${varName}:"][aria-disabled="true"]`;
318
+
319
+ const rule = `[style*="${varName}:"]${pseudoSelector} { ${cssProp}: var(${varName}) !important; ${baseVarRule} }`;
320
+ injectDynamicCss('pseudos', `pseudo:${varName}:${cssProp}`, rule);
321
+ }
322
+
323
+ function injectBreakpointRule(bp: string, varName: string, cssProp: string) {
324
+ const query = BREAKPOINT_QUERIES[bp];
325
+ if (!query) return;
326
+
327
+ const rule = `@media ${query} { [style*="${varName}:"] { ${cssProp}: var(${varName}) !important; } }`;
328
+ injectDynamicCss(bp, `bp:${bp}:${varName}:${cssProp}`, rule);
329
+ }
330
+
331
+ function injectNestedPseudoBreakpointRule(bp: string, pseudo: string, varName: string, cssProp: string) {
332
+ const query = BREAKPOINT_QUERIES[bp];
333
+ if (!query) return;
334
+
335
+ const baseVarRule = getBaseVarRule(pseudo, varName, cssProp);
336
+
337
+ // Dark mode menggunakan ancestor class selector dalam media query
338
+ if (pseudo === 'dark') {
339
+ const selector = `.dark [style*="${varName}:"], .dark[style*="${varName}:"], [data-theme="dark"] [style*="${varName}:"], [data-theme="dark"][style*="${varName}:"]`;
340
+ const rule = `@media ${query} { ${selector} { ${cssProp}: var(${varName}) !important; ${baseVarRule} } }`;
341
+ injectDynamicCss(bp, `nested:${bp}:${pseudo}:${varName}:${cssProp}`, rule);
342
+ return;
343
+ }
344
+
345
+ let pseudoSelector = `:hover`;
346
+ if (pseudo === 'active') pseudoSelector = `:active`;
347
+ else if (pseudo === 'focus') pseudoSelector = `:focus, [style*="${varName}:"]:focus-visible`;
348
+ else if (pseudo === 'disabled') pseudoSelector = `:disabled, [style*="${varName}:"][aria-disabled="true"]`;
349
+
350
+ const rule = `@media ${query} { [style*="${varName}:"]${pseudoSelector} { ${cssProp}: var(${varName}) !important; ${baseVarRule} } }`;
351
+ injectDynamicCss(bp, `nested:${bp}:${pseudo}:${varName}:${cssProp}`, rule);
352
+ }
353
+
354
+ /**
355
+ * Mengambil properti CSS native biasa (seperti color, fontSize, letterSpacing, transform, dll.)
356
+ * untuk langsung diterapkan sebagai inline style native browser.
357
+ * Mengabaikan pseudo-classes ('hover', 'focus') dan responsive breakpoints ('sm', 'md', dll.).
358
+ */
359
+ export function extractBaseStyles(ui?: UiProps | null): Record<string, any> {
360
+ if (!ui || typeof ui !== 'object') return {};
361
+ const base: Record<string, any> = {};
362
+ for (const [key, value] of Object.entries(ui)) {
363
+ if (value === undefined || value === null) continue;
364
+ if (PSEUDOS.includes(key as any) || BREAKPOINTS.includes(key as any)) continue;
365
+ if (typeof value === 'object') continue;
366
+
367
+ const cleaned = typeof value === 'string' ? cleanUiValue(value) : value;
368
+
369
+ // Support alias
370
+ if (key === 'bg') {
371
+ base['backgroundColor'] = cleaned;
372
+ } else if (key === 'size') {
373
+ base['fontSize'] = cleaned;
374
+ } else if (key === 'weight') {
375
+ base['fontWeight'] = cleaned;
376
+ } else {
377
+ base[key] = cleaned;
378
+ }
379
+ }
380
+ return base;
381
+ }
382
+
383
+ /**
384
+ * buildUiVars — konversi UiProps ke CSS custom properties untuk dipakai di style attribute
385
+ * sekaligus mendaftarkan aturan dinamis (pseudo & responsive media queries) secara otomatis.
386
+ */
387
+ export function buildUiVars(ui?: UiProps | null, prefix = '--ttdk-ui'): Record<string, any> {
388
+ if (!ui) return {};
389
+ const vars: Record<string, any> = { ...extractBaseStyles(ui) };
390
+
391
+ // 1. Base props
392
+ for (const [key, value] of Object.entries(ui)) {
393
+ if (value === undefined || value === null) continue;
394
+ if (PSEUDOS.includes(key as any) || BREAKPOINTS.includes(key as any)) continue;
395
+ if (typeof value === 'object') continue;
396
+ const strVal = cleanUiValue(value);
397
+ vars[`${prefix}-${toKebab(key)}`] = strVal;
398
+ if (key === 'bg') {
399
+ vars[`${prefix}-background`] = strVal;
400
+ vars[`${prefix}-background-color`] = strVal;
401
+ } else if (key === 'background' || key === 'backgroundColor') {
402
+ vars[`${prefix}-background`] = strVal;
403
+ vars[`${prefix}-bg`] = strVal;
404
+ } else if (key === 'size') {
405
+ vars[`${prefix}-font-size`] = strVal;
406
+ } else if (key === 'weight') {
407
+ vars[`${prefix}-font-weight`] = strVal;
408
+ }
409
+ }
410
+
411
+ // 2. Pseudo-classes at root: hover: { color: 'red', md: { color: 'blue' } }
412
+ for (const pseudo of PSEUDOS) {
413
+ const pseudoObj = (ui as any)[pseudo];
414
+ if (!pseudoObj || typeof pseudoObj !== 'object') continue;
415
+ for (const [key, value] of Object.entries(pseudoObj)) {
416
+ if (value === undefined || value === null) continue;
417
+
418
+ // Nested breakpoint inside pseudo: hover: { md: { background: 'blue' } }
419
+ if (BREAKPOINTS.includes(key as any)) {
420
+ const bpObj = value as any;
421
+ if (bpObj && typeof bpObj === 'object') {
422
+ for (const [subKey, subVal] of Object.entries(bpObj)) {
423
+ if (subVal === undefined || subVal === null || typeof subVal === 'object') continue;
424
+ const kebab = toKebab(subKey);
425
+ const varName = `${prefix}-${pseudo}-${kebab}-${key}`;
426
+ vars[varName] = String(subVal);
427
+ injectNestedPseudoBreakpointRule(key, pseudo, varName, resolveCssProperty(subKey));
428
+
429
+ if (subKey === 'bg') {
430
+ const bgVar = `${prefix}-${pseudo}-background-${key}`;
431
+ vars[bgVar] = String(subVal);
432
+ injectNestedPseudoBreakpointRule(key, pseudo, bgVar, 'background');
433
+ const bgColVar = `${prefix}-${pseudo}-background-color-${key}`;
434
+ vars[bgColVar] = String(subVal);
435
+ injectNestedPseudoBreakpointRule(key, pseudo, bgColVar, 'background-color');
436
+ } else if (subKey === 'background' || subKey === 'backgroundColor') {
437
+ const bgVar = `${prefix}-${pseudo}-background-${key}`;
438
+ vars[bgVar] = String(subVal);
439
+ injectNestedPseudoBreakpointRule(key, pseudo, bgVar, 'background');
440
+ const bgShortVar = `${prefix}-${pseudo}-bg-${key}`;
441
+ vars[bgShortVar] = String(subVal);
442
+ injectNestedPseudoBreakpointRule(key, pseudo, bgShortVar, 'background');
443
+ }
444
+ }
445
+ }
446
+ continue;
447
+ }
448
+
449
+ if (typeof value === 'object') continue;
450
+ const kebab = toKebab(key);
451
+ const varName = `${prefix}-${pseudo}-${kebab}`;
452
+ vars[varName] = String(value);
453
+ injectPseudoRule(pseudo, varName, resolveCssProperty(key));
454
+
455
+ // alias: backgroundColor / bg
456
+ if (key === 'backgroundColor' || key === 'bg') {
457
+ const bgVar = `${prefix}-${pseudo}-background`;
458
+ vars[bgVar] = String(value);
459
+ injectPseudoRule(pseudo, bgVar, 'background');
460
+ const bgShortVar = `${prefix}-${pseudo}-bg`;
461
+ vars[bgShortVar] = String(value);
462
+ injectPseudoRule(pseudo, bgShortVar, 'background');
463
+ } else if (key === 'background') {
464
+ const bgShortVar = `${prefix}-${pseudo}-bg`;
465
+ vars[bgShortVar] = String(value);
466
+ injectPseudoRule(pseudo, bgShortVar, 'background');
467
+ }
468
+ }
469
+ }
470
+
471
+ // 3. Breakpoints at root: md: { background: 'teal', hover: { background: '#fae8ff' } }
472
+ for (const bp of BREAKPOINTS) {
473
+ const bpObj = (ui as any)[bp];
474
+ if (!bpObj || typeof bpObj !== 'object') continue;
475
+ for (const [key, value] of Object.entries(bpObj)) {
476
+ if (value === undefined || value === null) continue;
477
+
478
+ // Nested pseudo inside breakpoint: md: { hover: { background: '#fae8ff' } }
479
+ if (PSEUDOS.includes(key as any)) {
480
+ const pseudoObj = value as any;
481
+ if (pseudoObj && typeof pseudoObj === 'object') {
482
+ for (const [pseudoKey, pseudoVal] of Object.entries(pseudoObj)) {
483
+ if (pseudoVal === undefined || pseudoVal === null || typeof pseudoVal === 'object') continue;
484
+ const kebab = toKebab(pseudoKey);
485
+ const varName = `${prefix}-${key}-${kebab}-${bp}`;
486
+ vars[varName] = String(pseudoVal);
487
+ injectNestedPseudoBreakpointRule(bp, key, varName, resolveCssProperty(pseudoKey));
488
+
489
+ if (pseudoKey === 'bg') {
490
+ const bgVar = `${prefix}-${key}-background-${bp}`;
491
+ vars[bgVar] = String(pseudoVal);
492
+ injectNestedPseudoBreakpointRule(bp, key, bgVar, 'background');
493
+ const bgColVar = `${prefix}-${key}-background-color-${bp}`;
494
+ vars[bgColVar] = String(pseudoVal);
495
+ injectNestedPseudoBreakpointRule(bp, key, bgColVar, 'background-color');
496
+ } else if (pseudoKey === 'background' || pseudoKey === 'backgroundColor') {
497
+ const bgVar = `${prefix}-${key}-background-${bp}`;
498
+ vars[bgVar] = String(pseudoVal);
499
+ injectNestedPseudoBreakpointRule(bp, key, bgVar, 'background');
500
+ const bgShortVar = `${prefix}-${key}-bg-${bp}`;
501
+ vars[bgShortVar] = String(pseudoVal);
502
+ injectNestedPseudoBreakpointRule(bp, key, bgShortVar, 'background');
503
+ }
504
+ }
505
+ }
506
+ continue;
507
+ }
508
+
509
+ if (typeof value === 'object') continue;
510
+ const kebab = toKebab(key);
511
+ const varName = `${prefix}-${kebab}-${bp}`;
512
+ vars[varName] = String(value);
513
+ injectBreakpointRule(bp, varName, resolveCssProperty(key));
514
+
515
+ if (key === 'bg') {
516
+ const bgVar = `${prefix}-background-${bp}`;
517
+ vars[bgVar] = String(value);
518
+ injectBreakpointRule(bp, bgVar, 'background');
519
+ const bgColVar = `${prefix}-background-color-${bp}`;
520
+ vars[bgColVar] = String(value);
521
+ injectBreakpointRule(bp, bgColVar, 'background-color');
522
+ } else if (key === 'background' || key === 'backgroundColor') {
523
+ const bgVar = `${prefix}-background-${bp}`;
524
+ vars[bgVar] = String(value);
525
+ injectBreakpointRule(bp, bgVar, 'background');
526
+ const bgShortVar = `${prefix}-bg-${bp}`;
527
+ vars[bgShortVar] = String(value);
528
+ injectBreakpointRule(bp, bgShortVar, 'background');
529
+ } else if (key === 'size') {
530
+ const sizeVar = `${prefix}-font-size-${bp}`;
531
+ vars[sizeVar] = String(value);
532
+ injectBreakpointRule(bp, sizeVar, 'font-size');
533
+ } else if (key === 'weight') {
534
+ const weightVar = `${prefix}-font-weight-${bp}`;
535
+ vars[weightVar] = String(value);
536
+ injectBreakpointRule(bp, weightVar, 'font-weight');
537
+ }
538
+ }
539
+ }
540
+
541
+ return vars;
542
+ }
543
+
544
+ /**
545
+ * buildUiStyle — Solusi HYBRID:
546
+ * Menggabungkan native inline style (agar ratusan CSS properties native langsung jalan 100%)
547
+ * dengan CSS custom properties (agar :hover dan responsive media query tetap bekerja sempurna).
548
+ */
549
+ export function buildUiStyle(ui?: UiProps | null, prefix = '--ttdk-ui'): Record<string, any> {
550
+ return buildUiVars(ui, prefix);
551
+ }
552
+
553
+ // ============================================================
554
+ // Layout Helpers for Flex & Grid
555
+ // ============================================================
556
+
557
+ function mapFlexJustify(val?: string) {
558
+ if (!val) return undefined;
559
+ if (val === 'start') return 'flex-start';
560
+ if (val === 'end') return 'flex-end';
561
+ if (val === 'around') return 'space-around';
562
+ if (val === 'between') return 'space-between';
563
+ if (val === 'evenly') return 'space-evenly';
564
+ return val;
565
+ }
566
+
567
+ function mapFlexAlign(val?: string) {
568
+ if (!val) return undefined;
569
+ if (val === 'start') return 'flex-start';
570
+ if (val === 'end') return 'flex-end';
571
+ return val;
572
+ }
573
+
574
+ function formatGap(val?: string | number) {
575
+ if (val === undefined || val === null || val === '') return undefined;
576
+ if (typeof val === 'number') return `${val}px`;
577
+ return String(val);
578
+ }
579
+
580
+ export function buildFlexVars(props: any): Record<string, string> {
581
+ if (!props) return {};
582
+ const vars: Record<string, string> = {};
583
+
584
+ // Base
585
+ if (props.direction) vars['--ttdk-flex-direction'] = props.direction;
586
+ if (props.justifyContent) vars['--ttdk-flex-justify-content'] = mapFlexJustify(props.justifyContent) || props.justifyContent;
587
+ if (props.alignItems) vars['--ttdk-flex-align-items'] = mapFlexAlign(props.alignItems) || props.alignItems;
588
+ if (props.gap !== undefined) {
589
+ const g = formatGap(props.gap);
590
+ if (g) vars['--ttdk-flex-gap'] = g;
591
+ }
592
+ if (props.wrap) vars['--ttdk-flex-wrap'] = props.wrap;
593
+
594
+ // Breakpoints
595
+ const bps = ['sm', 'md', 'lg', 'xl', '2xl'] as const;
596
+ for (const bp of bps) {
597
+ const override = props[bp] || (bp === '2xl' ? (props['xxl'] || props['2xl']) : undefined);
598
+ if (override && typeof override === 'object') {
599
+ if (override.direction) vars[`--ttdk-flex-direction-${bp}`] = override.direction;
600
+ if (override.justifyContent) vars[`--ttdk-flex-justify-content-${bp}`] = mapFlexJustify(override.justifyContent) || override.justifyContent;
601
+ if (override.alignItems) vars[`--ttdk-flex-align-items-${bp}`] = mapFlexAlign(override.alignItems) || override.alignItems;
602
+ if (override.gap !== undefined) {
603
+ const g = formatGap(override.gap);
604
+ if (g) vars[`--ttdk-flex-gap-${bp}`] = g;
605
+ }
606
+ if (override.wrap) vars[`--ttdk-flex-wrap-${bp}`] = override.wrap;
607
+ }
608
+ }
609
+
610
+ return vars;
611
+ }
612
+
613
+ function mapFlexAlignSelf(val?: string) {
614
+ if (!val) return undefined;
615
+ if (val === 'start') return 'flex-start';
616
+ if (val === 'end') return 'flex-end';
617
+ return val;
618
+ }
619
+
620
+ function formatFlexBasis(val?: string | number) {
621
+ if (val === undefined || val === null || val === '') return undefined;
622
+ if (typeof val === 'number') return `${val}px`;
623
+ return String(val);
624
+ }
625
+
626
+ function formatFlexGrowShrink(val?: number | boolean) {
627
+ if (val === undefined || val === null) return undefined;
628
+ if (typeof val === 'boolean') return val ? '1' : '0';
629
+ return String(val);
630
+ }
631
+
632
+ export function buildFlexItemVars(props: any): Record<string, string> {
633
+ if (!props) return {};
634
+ const vars: Record<string, string> = {};
635
+
636
+ // Base
637
+ if (props.order !== undefined) vars['--ttdk-flex-item-order'] = String(props.order);
638
+ if (props.grow !== undefined) {
639
+ const g = formatFlexGrowShrink(props.grow);
640
+ if (g) vars['--ttdk-flex-item-grow'] = g;
641
+ }
642
+ if (props.shrink !== undefined) {
643
+ const s = formatFlexGrowShrink(props.shrink);
644
+ if (s) vars['--ttdk-flex-item-shrink'] = s;
645
+ }
646
+ if (props.basis !== undefined) {
647
+ const b = formatFlexBasis(props.basis);
648
+ if (b) vars['--ttdk-flex-item-basis'] = b;
649
+ }
650
+ if (props.alignSelf) vars['--ttdk-flex-item-align-self'] = mapFlexAlignSelf(props.alignSelf) || props.alignSelf;
651
+
652
+ // Breakpoints
653
+ const bps = ['sm', 'md', 'lg', 'xl', '2xl'] as const;
654
+ for (const bp of bps) {
655
+ const override = props[bp] || (bp === '2xl' ? (props['xxl'] || props['2xl']) : undefined);
656
+ if (override && typeof override === 'object') {
657
+ if (override.order !== undefined) vars[`--ttdk-flex-item-order-${bp}`] = String(override.order);
658
+ if (override.grow !== undefined) {
659
+ const g = formatFlexGrowShrink(override.grow);
660
+ if (g) vars[`--ttdk-flex-item-grow-${bp}`] = g;
661
+ }
662
+ if (override.shrink !== undefined) {
663
+ const s = formatFlexGrowShrink(override.shrink);
664
+ if (s) vars[`--ttdk-flex-item-shrink-${bp}`] = s;
665
+ }
666
+ if (override.basis !== undefined) {
667
+ const b = formatFlexBasis(override.basis);
668
+ if (b) vars[`--ttdk-flex-item-basis-${bp}`] = b;
669
+ }
670
+ if (override.alignSelf) vars[`--ttdk-flex-item-align-self-${bp}`] = mapFlexAlignSelf(override.alignSelf) || override.alignSelf;
671
+ }
672
+ }
673
+
674
+ return vars;
675
+ }
676
+
677
+ function formatGridTemplate(val?: string | number) {
678
+ if (val === undefined || val === null || val === '') return undefined;
679
+ if (typeof val === 'number') return `repeat(${val}, minmax(0, 1fr))`;
680
+ return String(val);
681
+ }
682
+
683
+ function mapGridJustifyContent(val?: string) {
684
+ if (!val) return undefined;
685
+ if (val === 'start') return 'start';
686
+ if (val === 'center') return 'center';
687
+ if (val === 'end') return 'end';
688
+ if (val === 'around') return 'space-around';
689
+ if (val === 'between') return 'space-between';
690
+ if (val === 'evenly') return 'space-evenly';
691
+ return val;
692
+ }
693
+
694
+ function mapGridAlignContent(val?: string) {
695
+ if (!val) return undefined;
696
+ if (val === 'start') return 'start';
697
+ if (val === 'center') return 'center';
698
+ if (val === 'end') return 'end';
699
+ if (val === 'around') return 'space-around';
700
+ if (val === 'between') return 'space-between';
701
+ if (val === 'evenly') return 'space-evenly';
702
+ return val;
703
+ }
704
+
705
+ export function buildGridVars(props: any): Record<string, string> {
706
+ if (!props) return {};
707
+ const vars: Record<string, string> = {};
708
+
709
+ // Base
710
+ if (props.templateColumns !== undefined) {
711
+ const tc = formatGridTemplate(props.templateColumns);
712
+ if (tc) vars['--ttdk-grid-template-columns'] = tc;
713
+ }
714
+ if (props.templateRows !== undefined) {
715
+ const tr = formatGridTemplate(props.templateRows);
716
+ if (tr) vars['--ttdk-grid-template-rows'] = tr;
717
+ }
718
+ if (props.gap !== undefined) {
719
+ const g = formatGap(props.gap);
720
+ if (g) vars['--ttdk-grid-gap'] = g;
721
+ }
722
+ if (props.justifyContent) {
723
+ vars['--ttdk-grid-justify-content'] = mapGridJustifyContent(props.justifyContent) || props.justifyContent;
724
+ }
725
+ if (props.alignContent) {
726
+ vars['--ttdk-grid-align-content'] = mapGridAlignContent(props.alignContent) || props.alignContent;
727
+ }
728
+
729
+ // Breakpoints
730
+ const bps = ['sm', 'md', 'lg', 'xl', '2xl'] as const;
731
+ for (const bp of bps) {
732
+ const override = props[bp] || (bp === '2xl' ? (props['xxl'] || props['2xl']) : undefined);
733
+ if (override && typeof override === 'object') {
734
+ if (override.templateColumns !== undefined) {
735
+ const tc = formatGridTemplate(override.templateColumns);
736
+ if (tc) vars[`--ttdk-grid-template-columns-${bp}`] = tc;
737
+ }
738
+ if (override.templateRows !== undefined) {
739
+ const tr = formatGridTemplate(override.templateRows);
740
+ if (tr) vars[`--ttdk-grid-template-rows-${bp}`] = tr;
741
+ }
742
+ if (override.gap !== undefined) {
743
+ const g = formatGap(override.gap);
744
+ if (g) vars[`--ttdk-grid-gap-${bp}`] = g;
745
+ }
746
+ if (override.justifyContent) {
747
+ vars[`--ttdk-grid-justify-content-${bp}`] = mapGridJustifyContent(override.justifyContent) || override.justifyContent;
748
+ }
749
+ if (override.alignContent) {
750
+ vars[`--ttdk-grid-align-content-${bp}`] = mapGridAlignContent(override.alignContent) || override.alignContent;
751
+ }
752
+ }
753
+ }
754
+
755
+ return vars;
756
+ }
757
+
758
+ function formatGridColRow(val?: number | string) {
759
+ if (val === undefined || val === null || val === '') return undefined;
760
+ if (typeof val === 'number') return `span ${val} / span ${val}`;
761
+ if (val === 'auto') return 'auto';
762
+ if (val === 'full') return '1 / -1';
763
+ return String(val);
764
+ }
765
+
766
+ function resolveGridTrack(
767
+ span?: number | string,
768
+ start?: number | string,
769
+ end?: number | string
770
+ ): string | undefined {
771
+ const hasSpan = span !== undefined && span !== null && span !== '';
772
+ const hasStart = start !== undefined && start !== null && start !== '';
773
+ const hasEnd = end !== undefined && end !== null && end !== '';
774
+
775
+ if (!hasSpan && !hasStart && !hasEnd) return undefined;
776
+
777
+ if (hasStart && hasEnd) {
778
+ return `${start} / ${end}`;
779
+ }
780
+ if (hasStart && hasSpan) {
781
+ const spanStr = typeof span === 'number' ? `span ${span}` : String(span);
782
+ return `${start} / ${spanStr}`;
783
+ }
784
+ if (hasEnd && hasSpan) {
785
+ const spanStr = typeof span === 'number' ? `span ${span}` : String(span);
786
+ return `${spanStr} / ${end}`;
787
+ }
788
+ if (hasStart) {
789
+ return `${start} / auto`;
790
+ }
791
+ if (hasEnd) {
792
+ return `auto / ${end}`;
793
+ }
794
+ return formatGridColRow(span);
795
+ }
796
+
797
+ export function buildGridItemVars(props: any): Record<string, string> {
798
+ if (!props) return {};
799
+ const vars: Record<string, string> = {};
800
+
801
+ // Base
802
+ const colVal = resolveGridTrack(props.col, props.colStart, props.colEnd);
803
+ if (colVal) vars['--ttdk-grid-item-col'] = colVal;
804
+ if (props.colStart !== undefined) vars['--ttdk-grid-item-col-start'] = String(props.colStart);
805
+ if (props.colEnd !== undefined) vars['--ttdk-grid-item-col-end'] = String(props.colEnd);
806
+
807
+ const rowVal = resolveGridTrack(props.row, props.rowStart, props.rowEnd);
808
+ if (rowVal) vars['--ttdk-grid-item-row'] = rowVal;
809
+ if (props.rowStart !== undefined) vars['--ttdk-grid-item-row-start'] = String(props.rowStart);
810
+ if (props.rowEnd !== undefined) vars['--ttdk-grid-item-row-end'] = String(props.rowEnd);
811
+
812
+ if (props.justifySelf) vars['--ttdk-grid-item-justify-self'] = props.justifySelf;
813
+ if (props.order !== undefined) vars['--ttdk-grid-item-order'] = String(props.order);
814
+
815
+ // Breakpoints
816
+ const bps = ['sm', 'md', 'lg', 'xl', '2xl'] as const;
817
+ for (const bp of bps) {
818
+ const override = props[bp] || (bp === '2xl' ? (props['xxl'] || props['2xl']) : undefined);
819
+ if (override && typeof override === 'object') {
820
+ const bpColVal = resolveGridTrack(override.col, override.colStart, override.colEnd);
821
+ if (bpColVal) vars[`--ttdk-grid-item-col-${bp}`] = bpColVal;
822
+ if (override.colStart !== undefined) vars[`--ttdk-grid-item-col-start-${bp}`] = String(override.colStart);
823
+ if (override.colEnd !== undefined) vars[`--ttdk-grid-item-col-end-${bp}`] = String(override.colEnd);
824
+
825
+ const bpRowVal = resolveGridTrack(override.row, override.rowStart, override.rowEnd);
826
+ if (bpRowVal) vars[`--ttdk-grid-item-row-${bp}`] = bpRowVal;
827
+ if (override.rowStart !== undefined) vars[`--ttdk-grid-item-row-start-${bp}`] = String(override.rowStart);
828
+ if (override.rowEnd !== undefined) vars[`--ttdk-grid-item-row-end-${bp}`] = String(override.rowEnd);
829
+
830
+ if (override.justifySelf) vars[`--ttdk-grid-item-justify-self-${bp}`] = override.justifySelf;
831
+ if (override.order !== undefined) vars[`--ttdk-grid-item-order-${bp}`] = String(override.order);
832
+ }
833
+ }
834
+
835
+ return vars;
836
+ }