tekivex-ui 2.1.0 → 2.2.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 (32) hide show
  1. package/dist/index.cjs +12 -12
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1623 -1511
  5. package/dist/src/components/TkxConfigProvider.d.ts +100 -0
  6. package/dist/src/components/TkxConfigProvider.d.ts.map +1 -0
  7. package/dist/src/components/TkxEmpty.d.ts +870 -0
  8. package/dist/src/components/TkxEmpty.d.ts.map +1 -0
  9. package/dist/src/components/TkxForm.d.ts +54 -0
  10. package/dist/src/components/TkxForm.d.ts.map +1 -0
  11. package/dist/src/components/TkxLayout.d.ts +87 -0
  12. package/dist/src/components/TkxLayout.d.ts.map +1 -0
  13. package/dist/src/components/TkxSpin.d.ts +15 -0
  14. package/dist/src/components/TkxSpin.d.ts.map +1 -0
  15. package/dist/src/components/TkxStatistic.d.ts +1746 -0
  16. package/dist/src/components/TkxStatistic.d.ts.map +1 -0
  17. package/dist/src/components/TkxTypography.d.ts +2614 -0
  18. package/dist/src/components/TkxTypography.d.ts.map +1 -0
  19. package/dist/src/components/index.d.ts +7 -0
  20. package/dist/src/components/index.d.ts.map +1 -1
  21. package/dist/src/themes/index.d.ts +108 -0
  22. package/dist/src/themes/index.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/TkxConfigProvider.tsx +458 -0
  25. package/src/components/TkxEmpty.tsx +219 -0
  26. package/src/components/TkxForm.tsx +607 -0
  27. package/src/components/TkxLayout.tsx +647 -0
  28. package/src/components/TkxSpin.tsx +261 -0
  29. package/src/components/TkxStatistic.tsx +261 -0
  30. package/src/components/TkxTypography.tsx +263 -0
  31. package/src/components/index.ts +7 -0
  32. package/src/themes/index.ts +149 -0
@@ -0,0 +1,263 @@
1
+ import {
2
+ useState,
3
+ useCallback,
4
+ type ReactNode,
5
+ type CSSProperties,
6
+ createElement,
7
+ } from 'react';
8
+ import { useTheme } from '../themes';
9
+ import { sanitizeString } from '../engine/security';
10
+
11
+ // ── Interfaces ──────────────────────────────────────────────────────────────
12
+
13
+ export type TypographyType = 'default' | 'secondary' | 'success' | 'warning' | 'danger';
14
+
15
+ export interface TkxTitleProps {
16
+ level?: 1 | 2 | 3 | 4 | 5;
17
+ children: ReactNode;
18
+ copyable?: boolean;
19
+ type?: TypographyType;
20
+ style?: CSSProperties;
21
+ }
22
+
23
+ export interface TkxTextProps {
24
+ children: ReactNode;
25
+ type?: TypographyType;
26
+ strong?: boolean;
27
+ italic?: boolean;
28
+ underline?: boolean;
29
+ delete?: boolean;
30
+ code?: boolean;
31
+ mark?: boolean;
32
+ copyable?: boolean;
33
+ style?: CSSProperties;
34
+ }
35
+
36
+ export interface TkxParagraphProps {
37
+ children: ReactNode;
38
+ type?: 'default' | 'secondary';
39
+ copyable?: boolean;
40
+ ellipsis?: boolean | { rows?: number };
41
+ style?: CSSProperties;
42
+ }
43
+
44
+ // ── Helpers ─────────────────────────────────────────────────────────────────
45
+
46
+ function getTextContent(node: ReactNode): string {
47
+ if (typeof node === 'string') return node;
48
+ if (typeof node === 'number') return String(node);
49
+ if (Array.isArray(node)) return node.map(getTextContent).join('');
50
+ if (node && typeof node === 'object' && 'props' in node) {
51
+ return getTextContent((node as { props: { children?: ReactNode } }).props.children);
52
+ }
53
+ return '';
54
+ }
55
+
56
+ // ── CopyButton ──────────────────────────────────────────────────────────────
57
+
58
+ function CopyButton({ text }: { text: string }) {
59
+ const theme = useTheme();
60
+ const [copied, setCopied] = useState(false);
61
+
62
+ const handleCopy = useCallback(() => {
63
+ navigator.clipboard.writeText(text).then(() => {
64
+ setCopied(true);
65
+ setTimeout(() => setCopied(false), 2000);
66
+ });
67
+ }, [text]);
68
+
69
+ return createElement(
70
+ 'button',
71
+ {
72
+ type: 'button' as const,
73
+ onClick: handleCopy,
74
+ 'aria-label': copied ? 'Copied' : 'Copy to clipboard',
75
+ style: {
76
+ display: 'inline-flex',
77
+ alignItems: 'center',
78
+ marginLeft: '4px',
79
+ padding: '2px 6px',
80
+ border: 'none',
81
+ background: 'transparent',
82
+ color: copied ? theme.success : theme.textMuted,
83
+ cursor: 'pointer',
84
+ fontSize: '0.8em',
85
+ borderRadius: '4px',
86
+ transition: 'color 0.2s',
87
+ },
88
+ },
89
+ copied ? 'Copied!' : '\u2398',
90
+ );
91
+ }
92
+
93
+ // ── Color Map ───────────────────────────────────────────────────────────────
94
+
95
+ function useTypeColor(type: TypographyType = 'default') {
96
+ const theme = useTheme();
97
+ const map: Record<TypographyType, string> = {
98
+ default: theme.text,
99
+ secondary: theme.textMuted,
100
+ success: theme.success,
101
+ warning: theme.warning,
102
+ danger: theme.danger,
103
+ };
104
+ return map[type];
105
+ }
106
+
107
+ // ── Title Sizes ─────────────────────────────────────────────────────────────
108
+
109
+ const TITLE_SIZES: Record<number, { fontSize: string; lineHeight: string; fontWeight: number }> = {
110
+ 1: { fontSize: '2.25rem', lineHeight: '1.2', fontWeight: 700 },
111
+ 2: { fontSize: '1.875rem', lineHeight: '1.25', fontWeight: 700 },
112
+ 3: { fontSize: '1.5rem', lineHeight: '1.3', fontWeight: 600 },
113
+ 4: { fontSize: '1.25rem', lineHeight: '1.35', fontWeight: 600 },
114
+ 5: { fontSize: '1rem', lineHeight: '1.4', fontWeight: 600 },
115
+ };
116
+
117
+ // ── TkxTitle ────────────────────────────────────────────────────────────────
118
+
119
+ export function TkxTitle({
120
+ level = 1,
121
+ children,
122
+ copyable = false,
123
+ type = 'default',
124
+ style,
125
+ }: TkxTitleProps) {
126
+ const color = useTypeColor(type);
127
+ const theme = useTheme();
128
+ const sizeConfig = TITLE_SIZES[level];
129
+ const tag = `h${level}` as keyof JSX.IntrinsicElements;
130
+
131
+ const safeChildren = typeof children === 'string' ? sanitizeString(children) : children;
132
+ const textContent = getTextContent(children);
133
+
134
+ return createElement(
135
+ tag,
136
+ {
137
+ style: {
138
+ color,
139
+ fontSize: sizeConfig.fontSize,
140
+ lineHeight: sizeConfig.lineHeight,
141
+ fontWeight: sizeConfig.fontWeight,
142
+ margin: '0 0 0.5em 0',
143
+ fontFamily: 'inherit',
144
+ ...style,
145
+ },
146
+ },
147
+ safeChildren,
148
+ copyable && createElement(CopyButton, { text: textContent }),
149
+ );
150
+ }
151
+
152
+ // ── TkxText ─────────────────────────────────────────────────────────────────
153
+
154
+ export function TkxText({
155
+ children,
156
+ type = 'default',
157
+ strong = false,
158
+ italic = false,
159
+ underline = false,
160
+ delete: strikethrough = false,
161
+ code = false,
162
+ mark = false,
163
+ copyable = false,
164
+ style,
165
+ }: TkxTextProps) {
166
+ const color = useTypeColor(type);
167
+ const theme = useTheme();
168
+ const textContent = getTextContent(children);
169
+ const safeChildren = typeof children === 'string' ? sanitizeString(children) : children;
170
+
171
+ const decorations: string[] = [];
172
+ if (underline) decorations.push('underline');
173
+ if (strikethrough) decorations.push('line-through');
174
+
175
+ let content: ReactNode = safeChildren;
176
+
177
+ if (code) {
178
+ content = createElement(
179
+ 'code',
180
+ {
181
+ style: {
182
+ padding: '0.15em 0.4em',
183
+ fontSize: '0.875em',
184
+ backgroundColor: theme.surfaceAlt,
185
+ border: `1px solid ${theme.border}`,
186
+ borderRadius: '4px',
187
+ fontFamily: 'monospace',
188
+ },
189
+ },
190
+ content,
191
+ );
192
+ }
193
+
194
+ if (mark) {
195
+ content = createElement(
196
+ 'mark',
197
+ {
198
+ style: {
199
+ backgroundColor: `${theme.warning}33`,
200
+ color,
201
+ padding: '0 2px',
202
+ borderRadius: '2px',
203
+ },
204
+ },
205
+ content,
206
+ );
207
+ }
208
+
209
+ return createElement(
210
+ 'span',
211
+ {
212
+ style: {
213
+ color,
214
+ fontWeight: strong ? 600 : 'inherit',
215
+ fontStyle: italic ? 'italic' : 'normal',
216
+ textDecoration: decorations.length > 0 ? decorations.join(' ') : 'none',
217
+ ...style,
218
+ },
219
+ },
220
+ content,
221
+ copyable && createElement(CopyButton, { text: textContent }),
222
+ );
223
+ }
224
+
225
+ // ── TkxParagraph ────────────────────────────────────────────────────────────
226
+
227
+ export function TkxParagraph({
228
+ children,
229
+ type = 'default',
230
+ copyable = false,
231
+ ellipsis = false,
232
+ style,
233
+ }: TkxParagraphProps) {
234
+ const theme = useTheme();
235
+ const color = type === 'secondary' ? theme.textMuted : theme.text;
236
+ const textContent = getTextContent(children);
237
+ const safeChildren = typeof children === 'string' ? sanitizeString(children) : children;
238
+
239
+ const ellipsisStyle: CSSProperties = {};
240
+ if (ellipsis) {
241
+ const rows = typeof ellipsis === 'object' ? (ellipsis.rows ?? 3) : 3;
242
+ ellipsisStyle.display = '-webkit-box';
243
+ ellipsisStyle.WebkitLineClamp = rows;
244
+ ellipsisStyle.WebkitBoxOrient = 'vertical';
245
+ ellipsisStyle.overflow = 'hidden';
246
+ }
247
+
248
+ return createElement(
249
+ 'div',
250
+ {
251
+ style: {
252
+ color,
253
+ fontSize: '1rem',
254
+ lineHeight: '1.6',
255
+ margin: '0 0 1em 0',
256
+ ...ellipsisStyle,
257
+ ...style,
258
+ },
259
+ },
260
+ safeChildren,
261
+ copyable && createElement(CopyButton, { text: textContent }),
262
+ );
263
+ }
@@ -51,3 +51,10 @@ export * from './TkxSnackbar';
51
51
  export * from './TkxDataGrid';
52
52
  export * from './TkxMasonry';
53
53
  export * from './TkxRichTextDisplay';
54
+ export * from './TkxForm';
55
+ export * from './TkxLayout';
56
+ export * from './TkxConfigProvider';
57
+ export * from './TkxTypography';
58
+ export * from './TkxSpin';
59
+ export * from './TkxEmpty';
60
+ export * from './TkxStatistic';
@@ -109,3 +109,152 @@ export function ThemeProvider({ theme = quantumDark, children }: ThemeProviderPr
109
109
  export function useTheme(): ThemeTokens {
110
110
  return useContext(ThemeContext);
111
111
  }
112
+
113
+ // ── Color Palette Generator ─────────────────────────────────────────────────
114
+ // Generates 50-900 shades from a single hex color
115
+
116
+ function hexToHSL(hex: string): [number, number, number] {
117
+ const clean = hex.replace('#', '');
118
+ const r = parseInt(clean.slice(0, 2), 16) / 255;
119
+ const g = parseInt(clean.slice(2, 4), 16) / 255;
120
+ const b = parseInt(clean.slice(4, 6), 16) / 255;
121
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
122
+ let h = 0, s = 0;
123
+ const l = (max + min) / 2;
124
+ if (max !== min) {
125
+ const d = max - min;
126
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
127
+ switch (max) {
128
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
129
+ case g: h = ((b - r) / d + 2) / 6; break;
130
+ case b: h = ((r - g) / d + 4) / 6; break;
131
+ }
132
+ }
133
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
134
+ }
135
+
136
+ function hslToHex(h: number, s: number, l: number): string {
137
+ s /= 100; l /= 100;
138
+ const a = s * Math.min(l, 1 - l);
139
+ const f = (n: number) => {
140
+ const k = (n + h / 30) % 12;
141
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
142
+ return Math.round(255 * color).toString(16).padStart(2, '0');
143
+ };
144
+ return `#${f(0)}${f(8)}${f(4)}`;
145
+ }
146
+
147
+ export interface ColorPalette {
148
+ 50: string;
149
+ 100: string;
150
+ 200: string;
151
+ 300: string;
152
+ 400: string;
153
+ 500: string;
154
+ 600: string;
155
+ 700: string;
156
+ 800: string;
157
+ 900: string;
158
+ }
159
+
160
+ export function generatePalette(hex: string): ColorPalette {
161
+ const [h, s] = hexToHSL(hex);
162
+ return {
163
+ 50: hslToHex(h, Math.min(s, 30), 96),
164
+ 100: hslToHex(h, Math.min(s, 40), 90),
165
+ 200: hslToHex(h, Math.min(s, 50), 80),
166
+ 300: hslToHex(h, s, 70),
167
+ 400: hslToHex(h, s, 60),
168
+ 500: hex,
169
+ 600: hslToHex(h, s, 45),
170
+ 700: hslToHex(h, s, 35),
171
+ 800: hslToHex(h, s, 25),
172
+ 900: hslToHex(h, s, 15),
173
+ };
174
+ }
175
+
176
+ // ── Typography Scale ────────────────────────────────────────────────────────
177
+
178
+ export const typography = {
179
+ xs: { fontSize: '0.75rem', lineHeight: '1rem' },
180
+ sm: { fontSize: '0.875rem', lineHeight: '1.25rem' },
181
+ base: { fontSize: '1rem', lineHeight: '1.5rem' },
182
+ lg: { fontSize: '1.125rem', lineHeight: '1.75rem' },
183
+ xl: { fontSize: '1.25rem', lineHeight: '1.75rem' },
184
+ '2xl': { fontSize: '1.5rem', lineHeight: '2rem' },
185
+ '3xl': { fontSize: '1.875rem', lineHeight: '2.25rem' },
186
+ '4xl': { fontSize: '2.25rem', lineHeight: '2.5rem' },
187
+ '5xl': { fontSize: '3rem', lineHeight: '1.15' },
188
+ } as const;
189
+
190
+ // ── Spacing Scale ───────────────────────────────────────────────────────────
191
+
192
+ export const spacing = {
193
+ 0: '0px',
194
+ 0.5: '2px',
195
+ 1: '4px',
196
+ 1.5: '6px',
197
+ 2: '8px',
198
+ 2.5: '10px',
199
+ 3: '12px',
200
+ 4: '16px',
201
+ 5: '20px',
202
+ 6: '24px',
203
+ 8: '32px',
204
+ 10: '40px',
205
+ 12: '48px',
206
+ 16: '64px',
207
+ 20: '80px',
208
+ 24: '96px',
209
+ } as const;
210
+
211
+ // ── Breakpoints ─────────────────────────────────────────────────────────────
212
+
213
+ export const breakpoints = {
214
+ sm: 640,
215
+ md: 768,
216
+ lg: 1024,
217
+ xl: 1280,
218
+ '2xl': 1536,
219
+ } as const;
220
+
221
+ // ── Shadows ─────────────────────────────────────────────────────────────────
222
+
223
+ export const shadows = {
224
+ none: 'none',
225
+ xs: '0 1px 2px 0 rgba(0,0,0,0.05)',
226
+ sm: '0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1)',
227
+ md: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',
228
+ lg: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',
229
+ xl: '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)',
230
+ '2xl': '0 25px 50px -12px rgba(0,0,0,0.25)',
231
+ inner: 'inset 0 2px 4px 0 rgba(0,0,0,0.06)',
232
+ } as const;
233
+
234
+ // ── Z-Index Scale ───────────────────────────────────────────────────────────
235
+
236
+ export const zIndex = {
237
+ hide: -1,
238
+ base: 0,
239
+ dropdown: 1000,
240
+ sticky: 1100,
241
+ fixed: 1200,
242
+ overlay: 1300,
243
+ modal: 1400,
244
+ popover: 1500,
245
+ tooltip: 1600,
246
+ toast: 1700,
247
+ max: 9999,
248
+ } as const;
249
+
250
+ // ── Radius ──────────────────────────────────────────────────────────────────
251
+
252
+ export const radii = {
253
+ none: '0',
254
+ sm: '4px',
255
+ md: '6px',
256
+ lg: '8px',
257
+ xl: '12px',
258
+ '2xl': '16px',
259
+ full: '9999px',
260
+ } as const;