@ttdk-design-system/library-vue 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.
- package/AI_GUIDE.md +6518 -0
- package/llms.txt +6518 -0
- package/package.json +1096 -4
- package/src/core/index.ts +2 -0
- package/src/core/tailwind-preset.cjs +62 -0
- package/src/core/tailwind-preset.ts +85 -0
- package/src/core/theme/tokens.css +663 -0
- package/src/core/types.ts +836 -0
- package/src/index.ts +17 -0
- package/src/tailwind-preset.cjs +62 -0
- package/src/tailwind-preset.ts +85 -0
- package/src/types.ts +1 -0
- package/src/web/accordion/AI_GUIDE.md +149 -0
- package/src/web/accordion/accordion.css +1 -0
- package/src/web/accordion/accordion.vue +432 -0
- package/src/web/accordion/accordion.vue.d.ts +227 -0
- package/src/web/accordion/index.ts +3 -0
- package/src/web/accordion/manifest.json +351 -0
- package/src/web/alert/AI_GUIDE.md +139 -0
- package/src/web/alert/alert.css +1 -0
- package/src/web/alert/alert.vue +290 -0
- package/src/web/alert/alert.vue.d.ts +154 -0
- package/src/web/alert/index.ts +3 -0
- package/src/web/alert/manifest.json +240 -0
- package/src/web/avatar/AI_GUIDE.md +118 -0
- package/src/web/avatar/avatar.css +1 -0
- package/src/web/avatar/avatar.vue +217 -0
- package/src/web/avatar/avatar.vue.d.ts +135 -0
- package/src/web/avatar/index.ts +3 -0
- package/src/web/avatar/manifest.json +195 -0
- package/src/web/badge/AI_GUIDE.md +94 -0
- package/src/web/badge/badge.css +1 -0
- package/src/web/badge/badge.vue +135 -0
- package/src/web/badge/badge.vue.d.ts +108 -0
- package/src/web/badge/index.ts +3 -0
- package/src/web/badge/manifest.json +141 -0
- package/src/web/banner/AI_GUIDE.md +130 -0
- package/src/web/banner/banner.css +1 -0
- package/src/web/banner/banner.vue +303 -0
- package/src/web/banner/banner.vue.d.ts +162 -0
- package/src/web/banner/index.ts +3 -0
- package/src/web/banner/manifest.json +240 -0
- package/src/web/breadcrumb/AI_GUIDE.md +120 -0
- package/src/web/breadcrumb/breadcrumb.css +1 -0
- package/src/web/breadcrumb/breadcrumb.vue +313 -0
- package/src/web/breadcrumb/breadcrumb.vue.d.ts +139 -0
- package/src/web/breadcrumb/index.ts +3 -0
- package/src/web/breadcrumb/manifest.json +225 -0
- package/src/web/button/AI_GUIDE.md +121 -0
- package/src/web/button/button.css +1 -0
- package/src/web/button/button.vue +283 -0
- package/src/web/button/button.vue.d.ts +147 -0
- package/src/web/button/index.ts +3 -0
- package/src/web/button/manifest.json +177 -0
- package/src/web/calendar/AI_GUIDE.md +151 -0
- package/src/web/calendar/calendar.css +1 -0
- package/src/web/calendar/calendar.vue +1536 -0
- package/src/web/calendar/calendar.vue.d.ts +261 -0
- package/src/web/calendar/index.ts +3 -0
- package/src/web/calendar/manifest.json +470 -0
- package/src/web/card/AI_GUIDE.md +133 -0
- package/src/web/card/card.css +1 -0
- package/src/web/card/card.vue +423 -0
- package/src/web/card/card.vue.d.ts +232 -0
- package/src/web/card/index.ts +3 -0
- package/src/web/card/manifest.json +357 -0
- package/src/web/checkbox/AI_GUIDE.md +146 -0
- package/src/web/checkbox/checkbox.css +1 -0
- package/src/web/checkbox/checkbox.vue +304 -0
- package/src/web/checkbox/checkbox.vue.d.ts +189 -0
- package/src/web/checkbox/index.ts +3 -0
- package/src/web/checkbox/manifest.json +294 -0
- package/src/web/chip/AI_GUIDE.md +186 -0
- package/src/web/chip/chip.css +1 -0
- package/src/web/chip/chip.vue +220 -0
- package/src/web/chip/chip.vue.d.ts +140 -0
- package/src/web/chip/index.ts +3 -0
- package/src/web/chip/manifest.json +186 -0
- package/src/web/dropdown/AI_GUIDE.md +167 -0
- package/src/web/dropdown/dropdown.css +1 -0
- package/src/web/dropdown/dropdown.vue +1257 -0
- package/src/web/dropdown/dropdown.vue.d.ts +381 -0
- package/src/web/dropdown/index.ts +3 -0
- package/src/web/dropdown/manifest.json +576 -0
- package/src/web/flex/AI_GUIDE.md +113 -0
- package/src/web/flex/flex-item.vue +94 -0
- package/src/web/flex/flex-item.vue.d.ts +84 -0
- package/src/web/flex/flex.css +1 -0
- package/src/web/flex/flex.vue +103 -0
- package/src/web/flex/flex.vue.d.ts +92 -0
- package/src/web/flex/index.ts +5 -0
- package/src/web/flex/manifest.json +162 -0
- package/src/web/form/index.ts +7 -0
- package/src/web/grid/AI_GUIDE.md +112 -0
- package/src/web/grid/grid-item.vue +109 -0
- package/src/web/grid/grid-item.vue.d.ts +99 -0
- package/src/web/grid/grid.css +1 -0
- package/src/web/grid/grid.vue +94 -0
- package/src/web/grid/grid.vue.d.ts +84 -0
- package/src/web/grid/index.ts +5 -0
- package/src/web/grid/manifest.json +153 -0
- package/src/web/index.ts +86 -0
- package/src/web/input/AI_GUIDE.md +159 -0
- package/src/web/input/index.ts +3 -0
- package/src/web/input/input.css +1 -0
- package/src/web/input/input.vue +521 -0
- package/src/web/input/input.vue.d.ts +274 -0
- package/src/web/input/manifest.json +465 -0
- package/src/web/input-number/AI_GUIDE.md +164 -0
- package/src/web/input-number/index.ts +3 -0
- package/src/web/input-number/input-number.css +1 -0
- package/src/web/input-number/input-number.vue +1153 -0
- package/src/web/input-number/input-number.vue.d.ts +343 -0
- package/src/web/input-number/manifest.json +575 -0
- package/src/web/modal/AI_GUIDE.md +171 -0
- package/src/web/modal/index.ts +3 -0
- package/src/web/modal/manifest.json +375 -0
- package/src/web/modal/modal.css +1 -0
- package/src/web/modal/modal.vue +344 -0
- package/src/web/modal/modal.vue.d.ts +64 -0
- package/src/web/pagination/AI_GUIDE.md +148 -0
- package/src/web/pagination/index.ts +21 -0
- package/src/web/pagination/manifest.json +432 -0
- package/src/web/pagination/pagination-content.vue +42 -0
- package/src/web/pagination/pagination-content.vue.d.ts +28 -0
- package/src/web/pagination/pagination-ellipsis.vue +55 -0
- package/src/web/pagination/pagination-ellipsis.vue.d.ts +28 -0
- package/src/web/pagination/pagination-first.vue +161 -0
- package/src/web/pagination/pagination-first.vue.d.ts +67 -0
- package/src/web/pagination/pagination-item.vue +42 -0
- package/src/web/pagination/pagination-item.vue.d.ts +28 -0
- package/src/web/pagination/pagination-last.vue +161 -0
- package/src/web/pagination/pagination-last.vue.d.ts +67 -0
- package/src/web/pagination/pagination-link.vue +118 -0
- package/src/web/pagination/pagination-link.vue.d.ts +61 -0
- package/src/web/pagination/pagination-next.vue +160 -0
- package/src/web/pagination/pagination-next.vue.d.ts +68 -0
- package/src/web/pagination/pagination-previous.vue +160 -0
- package/src/web/pagination/pagination-previous.vue.d.ts +68 -0
- package/src/web/pagination/pagination.css +1 -0
- package/src/web/pagination/pagination.vue +730 -0
- package/src/web/pagination/pagination.vue.d.ts +247 -0
- package/src/web/popover/AI_GUIDE.md +163 -0
- package/src/web/popover/index.ts +3 -0
- package/src/web/popover/manifest.json +396 -0
- package/src/web/popover/popover.css +1 -0
- package/src/web/popover/popover.vue +579 -0
- package/src/web/popover/popover.vue.d.ts +83 -0
- package/src/web/radio/AI_GUIDE.md +155 -0
- package/src/web/radio/index.ts +3 -0
- package/src/web/radio/manifest.json +294 -0
- package/src/web/radio/radio.css +1 -0
- package/src/web/radio/radio.vue +288 -0
- package/src/web/radio/radio.vue.d.ts +182 -0
- package/src/web/selectbox/AI_GUIDE.md +216 -0
- package/src/web/selectbox/index.ts +3 -0
- package/src/web/selectbox/manifest.json +477 -0
- package/src/web/selectbox/selectbox.css +1 -0
- package/src/web/selectbox/selectbox.vue +1024 -0
- package/src/web/selectbox/selectbox.vue.d.ts +300 -0
- package/src/web/sonner/AI_GUIDE.md +98 -0
- package/src/web/sonner/index.ts +114 -0
- package/src/web/sonner/manifest.json +132 -0
- package/src/web/sonner/sonner.css +1 -0
- package/src/web/sonner/sonner.vue +180 -0
- package/src/web/sonner/sonner.vue.d.ts +113 -0
- package/src/web/table/AI_GUIDE.md +145 -0
- package/src/web/table/index.ts +34 -0
- package/src/web/table/manifest.json +168 -0
- package/src/web/table/table-body.vue +42 -0
- package/src/web/table/table-body.vue.d.ts +28 -0
- package/src/web/table/table-caption.vue +42 -0
- package/src/web/table/table-caption.vue.d.ts +28 -0
- package/src/web/table/table-cell.vue +71 -0
- package/src/web/table/table-cell.vue.d.ts +49 -0
- package/src/web/table/table-empty.vue +105 -0
- package/src/web/table/table-empty.vue.d.ts +57 -0
- package/src/web/table/table-footer.vue +42 -0
- package/src/web/table/table-footer.vue.d.ts +28 -0
- package/src/web/table/table-head.vue +78 -0
- package/src/web/table/table-head.vue.d.ts +50 -0
- package/src/web/table/table-header.vue +42 -0
- package/src/web/table/table-header.vue.d.ts +28 -0
- package/src/web/table/table-row.vue +70 -0
- package/src/web/table/table-row.vue.d.ts +44 -0
- package/src/web/table/table.css +1 -0
- package/src/web/table/table.vue +298 -0
- package/src/web/table/table.vue.d.ts +119 -0
- package/src/web/tabs/AI_GUIDE.md +150 -0
- package/src/web/tabs/index.ts +2 -0
- package/src/web/tabs/manifest.json +315 -0
- package/src/web/tabs/tabs.css +1 -0
- package/src/web/tabs/tabs.vue +164 -0
- package/src/web/tabs/tabs.vue.d.ts +60 -0
- package/src/web/text/AI_GUIDE.md +129 -0
- package/src/web/text/index.ts +2 -0
- package/src/web/text/manifest.json +195 -0
- package/src/web/text/text.css +1 -0
- package/src/web/text/text.vue +195 -0
- package/src/web/text/text.vue.d.ts +161 -0
- package/src/web/textarea/AI_GUIDE.md +141 -0
- package/src/web/textarea/index.ts +3 -0
- package/src/web/textarea/manifest.json +438 -0
- package/src/web/textarea/textarea.css +1 -0
- package/src/web/textarea/textarea.vue +458 -0
- package/src/web/textarea/textarea.vue.d.ts +265 -0
- package/src/web/tooltip/AI_GUIDE.md +110 -0
- package/src/web/tooltip/index.ts +3 -0
- package/src/web/tooltip/manifest.json +222 -0
- package/src/web/tooltip/tooltip.css +1 -0
- package/src/web/tooltip/tooltip.vue +417 -0
- package/src/web/tooltip/tooltip.vue.d.ts +150 -0
- package/src/webview/bottomsheet/AI_GUIDE.md +141 -0
- package/src/webview/bottomsheet/bottomsheet.css +1 -0
- package/src/webview/bottomsheet/bottomsheet.vue +441 -0
- package/src/webview/bottomsheet/bottomsheet.vue.d.ts +178 -0
- package/src/webview/bottomsheet/index.ts +4 -0
- package/src/webview/bottomsheet/manifest.json +377 -0
- package/src/webview/button/AI_GUIDE.md +79 -0
- package/src/webview/button/button-ios.css +1 -0
- package/src/webview/button/button-ios.vue +138 -0
- package/src/webview/button/button-ios.vue.d.ts +102 -0
- package/src/webview/button/button-material.css +1 -0
- package/src/webview/button/button-material.vue +138 -0
- package/src/webview/button/button-material.vue.d.ts +102 -0
- package/src/webview/button/index.ts +7 -0
- package/src/webview/button/manifest.json +148 -0
- package/src/webview/index.ts +7 -0
- package/tailwind-preset.cjs +62 -0
- package/tailwind-preset.js +62 -0
- package/ttdk-manifest.json +10188 -0
- 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
|
+
}
|