@shohojdhara/atomix 0.3.5 → 0.3.6
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/README.md +101 -199
- package/atomix.config.ts +241 -0
- package/dist/atomix.css +260 -179
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +250 -179
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js +61 -66
- package/dist/charts.js.map +1 -1
- package/dist/core.js +47 -31
- package/dist/core.js.map +1 -1
- package/dist/forms.js +47 -31
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +47 -31
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +1841 -1633
- package/dist/index.esm.js +4975 -4113
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +5151 -4290
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +1572 -1442
- package/dist/theme.js +4816 -4080
- package/dist/theme.js.map +1 -1
- package/package.json +6 -20
- package/src/components/Accordion/Accordion.stories.tsx +50 -17
- package/src/components/AtomixGlass/AtomixGlass.tsx +65 -31
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +11 -4
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +1 -32
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/shared-components.tsx +0 -31
- package/src/components/Avatar/Avatar.stories.tsx +7 -0
- package/src/components/Badge/Badge.stories.tsx +91 -13
- package/src/components/Block/Block.stories.tsx +7 -23
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +7 -0
- package/src/components/Button/Button.stories.tsx +141 -22
- package/src/components/Button/ButtonGroup.stories.tsx +315 -0
- package/src/components/Button/ButtonGroup.tsx +67 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Callout/Callout.stories.tsx +8 -6
- package/src/components/Card/Card.stories.tsx +82 -28
- package/src/components/Chart/AnimatedChart.tsx +0 -1
- package/src/components/Chart/AreaChart.tsx +0 -1
- package/src/components/Chart/BarChart.tsx +0 -1
- package/src/components/Chart/BubbleChart.tsx +0 -1
- package/src/components/Chart/CandlestickChart.tsx +0 -1
- package/src/components/Chart/Chart.stories.tsx +5 -7
- package/src/components/Chart/Chart.tsx +0 -16
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +0 -1
- package/src/components/Chart/FunnelChart.tsx +0 -1
- package/src/components/Chart/GaugeChart.tsx +0 -1
- package/src/components/Chart/HeatmapChart.tsx +0 -1
- package/src/components/Chart/LineChart.tsx +0 -1
- package/src/components/Chart/MultiAxisChart.tsx +0 -1
- package/src/components/Chart/PieChart.tsx +0 -1
- package/src/components/Chart/RadarChart.tsx +0 -1
- package/src/components/Chart/ScatterChart.tsx +0 -1
- package/src/components/Chart/WaterfallChart.tsx +0 -1
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +7 -0
- package/src/components/DataTable/DataTable.stories.tsx +23 -16
- package/src/components/DatePicker/DatePicker.stories.tsx +27 -19
- package/src/components/Dropdown/Dropdown.stories.tsx +11 -19
- package/src/components/EdgePanel/EdgePanel.stories.tsx +1 -0
- package/src/components/Footer/Footer.stories.tsx +8 -6
- package/src/components/Footer/FooterLink.tsx +9 -2
- package/src/components/Form/Checkbox.stories.tsx +7 -0
- package/src/components/Form/Form.stories.tsx +7 -0
- package/src/components/Form/FormGroup.stories.tsx +9 -1
- package/src/components/Form/Input.stories.tsx +69 -16
- package/src/components/Form/Radio.stories.tsx +9 -1
- package/src/components/Form/Select.stories.tsx +9 -1
- package/src/components/Form/Textarea.stories.tsx +10 -2
- package/src/components/Hero/Hero.stories.tsx +7 -0
- package/src/components/List/List.stories.tsx +7 -0
- package/src/components/Messages/Messages.stories.tsx +8 -7
- package/src/components/Modal/Modal.stories.tsx +17 -6
- package/src/components/Navigation/Menu/Menu.stories.tsx +7 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +7 -0
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +1 -0
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +188 -111
- package/src/components/Pagination/Pagination.tsx +83 -3
- package/src/components/PhotoViewer/PhotoViewer.stories.tsx +10 -5
- package/src/components/Popover/Popover.stories.tsx +191 -115
- package/src/components/ProductReview/ProductReview.stories.tsx +80 -58
- package/src/components/Progress/Progress.stories.tsx +79 -49
- package/src/components/Rating/Rating.stories.tsx +109 -84
- package/src/components/River/River.stories.tsx +194 -114
- package/src/components/SectionIntro/SectionIntro.stories.tsx +19 -9
- package/src/components/Slider/Slider.stories.tsx +7 -0
- package/src/components/Spinner/Spinner.stories.tsx +15 -11
- package/src/components/Steps/Steps.stories.tsx +132 -98
- package/src/components/Tabs/Tabs.stories.tsx +163 -112
- package/src/components/Testimonial/Testimonial.stories.tsx +114 -68
- package/src/components/Todo/Todo.stories.tsx +38 -12
- package/src/components/Toggle/Toggle.stories.tsx +61 -28
- package/src/components/Tooltip/Tooltip.stories.tsx +318 -200
- package/src/components/Upload/Upload.stories.tsx +122 -84
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +7 -24
- package/src/components/index.ts +1 -0
- package/src/lib/composables/useAtomixGlass.ts +2 -3
- package/src/lib/composables/useNavbar.ts +0 -10
- package/src/lib/config/loader.ts +2 -1
- package/src/lib/constants/components.ts +10 -0
- package/src/lib/hooks/useComponentCustomization.ts +1 -1
- package/src/lib/theme/README.md +174 -0
- package/src/lib/theme/adapters/index.ts +31 -0
- package/src/lib/theme/adapters/themeAdapter.ts +287 -0
- package/src/lib/theme/config/__tests__/configLoader.test.ts +207 -0
- package/src/lib/theme/config/configLoader.ts +254 -0
- package/src/lib/theme/config/loader.ts +37 -48
- package/src/lib/theme/config/types.ts +2 -2
- package/src/lib/theme/config/validator.ts +15 -91
- package/src/lib/theme/{constants.ts → constants/constants.ts} +0 -18
- package/src/lib/theme/constants/index.ts +8 -0
- package/src/lib/theme/core/ThemeRegistry.ts +19 -6
- package/src/lib/theme/core/__tests__/createTheme.test.ts +132 -0
- package/src/lib/theme/core/composeTheme.ts +155 -0
- package/src/lib/theme/core/createTheme.ts +94 -0
- package/src/lib/theme/{createTheme.ts → core/createThemeObject.ts} +10 -6
- package/src/lib/theme/core/index.ts +5 -19
- package/src/lib/theme/devtools/Comparator.tsx +346 -22
- package/src/lib/theme/devtools/IMPROVEMENTS.md +139 -38
- package/src/lib/theme/devtools/Inspector.tsx +335 -51
- package/src/lib/theme/devtools/LiveEditor.tsx +478 -107
- package/src/lib/theme/devtools/Preview.tsx +471 -221
- package/src/lib/theme/{core → devtools}/ThemeValidator.ts +1 -1
- package/src/lib/theme/devtools/index.ts +14 -4
- package/src/lib/theme/devtools/useHistory.ts +130 -0
- package/src/lib/theme/errors/index.ts +12 -0
- package/src/lib/theme/generators/cssFile.ts +79 -0
- package/src/lib/theme/generators/generateCSS.ts +89 -0
- package/src/lib/theme/{generateCSSVariables.ts → generators/generateCSSVariables.ts} +3 -13
- package/src/lib/theme/generators/index.ts +19 -0
- package/src/lib/theme/i18n/rtl.ts +5 -6
- package/src/lib/theme/index.ts +120 -15
- package/src/lib/theme/runtime/ThemeApplicator.ts +52 -111
- package/src/lib/theme/{ThemeContext.tsx → runtime/ThemeContext.tsx} +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +1 -1
- package/src/lib/theme/runtime/ThemeProvider.tsx +456 -179
- package/src/lib/theme/runtime/index.ts +1 -2
- package/src/lib/theme/runtime/useTheme.ts +1 -2
- package/src/lib/theme/test/testTheme.ts +385 -0
- package/src/lib/theme/tokens/index.ts +12 -0
- package/src/lib/theme/tokens/tokens.ts +721 -0
- package/src/lib/theme/types.ts +6 -42
- package/src/lib/theme/{utils.ts → utils/domUtils.ts} +2 -2
- package/src/lib/theme/utils/index.ts +11 -0
- package/src/lib/theme/utils/injectCSS.ts +90 -0
- package/src/lib/theme/utils/themeHelpers.ts +78 -0
- package/src/lib/theme/{themeUtils.ts → utils/themeUtils.ts} +1 -1
- package/src/lib/theme-tools.ts +7 -8
- package/src/lib/types/components.ts +40 -130
- package/src/lib/utils/componentUtils.ts +1 -1
- package/src/styles/01-settings/_settings.design-tokens.scss +4 -1
- package/src/styles/02-tools/_tools.button.scss +66 -79
- package/src/styles/06-components/_components.atomix-glass.scss +13 -3
- package/src/styles/06-components/_components.pagination.scss +88 -0
- package/scripts/sync-theme-config.js +0 -309
- package/src/lib/theme/composeTheme.ts +0 -370
- package/src/lib/theme/core/ThemeCache.ts +0 -283
- package/src/lib/theme/core/ThemeEngine.test.ts +0 -146
- package/src/lib/theme/core/ThemeEngine.ts +0 -665
- package/src/lib/theme/createThemeFromConfig.ts +0 -132
- package/src/lib/theme/devtools/CLI.ts +0 -364
- package/src/lib/theme/runtime/ThemeManager.test.ts +0 -192
- package/src/lib/theme/runtime/ThemeManager.ts +0 -446
- package/src/styles/03-generic/_generated-root.css +0 -26
- package/src/themes/README.md +0 -442
- package/src/themes/themes.config.js +0 -68
- /package/src/lib/theme/{cssVariableMapper.ts → adapters/cssVariableMapper.ts} +0 -0
- /package/src/lib/theme/{errors.ts → errors/errors.ts} +0 -0
|
@@ -249,44 +249,6 @@ import {
|
|
|
249
249
|
|
|
250
250
|
---
|
|
251
251
|
|
|
252
|
-
## Migration Guide
|
|
253
|
-
|
|
254
|
-
### For Existing Users
|
|
255
|
-
|
|
256
|
-
No breaking changes! All existing code continues to work.
|
|
257
|
-
|
|
258
|
-
**Before:**
|
|
259
|
-
```tsx
|
|
260
|
-
import { ThemeInspector } from '@shohojdhara/atomix/theme/devtools';
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
**After (still works):**
|
|
264
|
-
```tsx
|
|
265
|
-
import { ThemeInspector } from '@shohojdhara/atomix/theme/devtools';
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
**New (also works):**
|
|
269
|
-
```tsx
|
|
270
|
-
import { ThemeInspector } from '@shohojdhara/atomix/theme';
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### New Features
|
|
274
|
-
|
|
275
|
-
Simply import and use the new components:
|
|
276
|
-
|
|
277
|
-
```tsx
|
|
278
|
-
import {
|
|
279
|
-
ThemeComparator,
|
|
280
|
-
ThemeLiveEditor,
|
|
281
|
-
} from '@shohojdhara/atomix/theme';
|
|
282
|
-
|
|
283
|
-
// Use them immediately
|
|
284
|
-
<ThemeComparator themeA={theme1} themeB={theme2} />
|
|
285
|
-
<ThemeLiveEditor initialTheme={theme} />
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
252
|
## Future Enhancements
|
|
291
253
|
|
|
292
254
|
### Planned Features
|
|
@@ -318,6 +280,145 @@ import {
|
|
|
318
280
|
|
|
319
281
|
---
|
|
320
282
|
|
|
283
|
+
## Phase 2: Enhanced DevTools Features (2025-01-27)
|
|
284
|
+
|
|
285
|
+
### Overview
|
|
286
|
+
|
|
287
|
+
This phase focuses on enhancing the core devtools components (`ThemeLiveEditor`, `ThemePreview`, `ThemeInspector`, `ThemeComparator`) with advanced features inspired by professional theme studio implementations.
|
|
288
|
+
|
|
289
|
+
### Phase 1: Enhanced ThemeLiveEditor
|
|
290
|
+
|
|
291
|
+
**New Features:**
|
|
292
|
+
|
|
293
|
+
1. **Undo/Redo System**
|
|
294
|
+
- History stack for theme changes
|
|
295
|
+
- Keyboard shortcuts (Ctrl+Z / Ctrl+Shift+Z)
|
|
296
|
+
- Visual history indicator
|
|
297
|
+
- Maximum history size (50 entries)
|
|
298
|
+
|
|
299
|
+
2. **Keyboard Shortcuts**
|
|
300
|
+
- `Ctrl+Z` / `Cmd+Z` - Undo
|
|
301
|
+
- `Ctrl+Shift+Z` / `Cmd+Shift+Z` - Redo
|
|
302
|
+
- `Ctrl+S` / `Cmd+S` - Save/Export theme
|
|
303
|
+
- `Ctrl+/` / `Cmd+/` - Toggle edit mode
|
|
304
|
+
- `Escape` - Clear errors
|
|
305
|
+
|
|
306
|
+
3. **Resizable Layout**
|
|
307
|
+
- Drag-to-resize split between editor and preview
|
|
308
|
+
- Persistent layout preferences (localStorage)
|
|
309
|
+
- Minimum panel sizes enforced
|
|
310
|
+
- Smooth resize animations
|
|
311
|
+
|
|
312
|
+
4. **Enhanced Color Pickers**
|
|
313
|
+
- Alpha channel support (RGBA)
|
|
314
|
+
- Multiple format support (hex, rgb, hsl)
|
|
315
|
+
- Color format conversion
|
|
316
|
+
- Color history/palette
|
|
317
|
+
- Contrast checker integration
|
|
318
|
+
|
|
319
|
+
**Implementation Details:**
|
|
320
|
+
- Custom `useHistory` hook for undo/redo
|
|
321
|
+
- Resizable panels using mouse drag events
|
|
322
|
+
- Enhanced color input with format detection
|
|
323
|
+
- Keyboard event listeners with proper cleanup
|
|
324
|
+
|
|
325
|
+
### Phase 2: Enhanced ThemePreview
|
|
326
|
+
|
|
327
|
+
**New Features:**
|
|
328
|
+
|
|
329
|
+
1. **Interactive Components**
|
|
330
|
+
- Hover states on all interactive elements
|
|
331
|
+
- Focus states for accessibility
|
|
332
|
+
- Active/pressed states for buttons
|
|
333
|
+
- Click interactions with visual feedback
|
|
334
|
+
|
|
335
|
+
2. **Viewport Controls**
|
|
336
|
+
- Mobile viewport simulation (375px, 414px)
|
|
337
|
+
- Tablet viewport simulation (768px, 1024px)
|
|
338
|
+
- Desktop viewport simulation (1280px, 1920px)
|
|
339
|
+
- Custom viewport size input
|
|
340
|
+
- Responsive breakpoint indicators
|
|
341
|
+
|
|
342
|
+
3. **Dark Mode Toggle**
|
|
343
|
+
- Independent dark mode preview (separate from system)
|
|
344
|
+
- Smooth theme transition
|
|
345
|
+
- Preview-specific theme override
|
|
346
|
+
- Toggle button with visual indicator
|
|
347
|
+
|
|
348
|
+
**Implementation Details:**
|
|
349
|
+
- Interactive state management for preview components
|
|
350
|
+
- Viewport wrapper with responsive controls
|
|
351
|
+
- Theme mode state independent of system preferences
|
|
352
|
+
- CSS transitions for smooth mode switching
|
|
353
|
+
|
|
354
|
+
### Phase 3: Enhanced ThemeInspector
|
|
355
|
+
|
|
356
|
+
**New Features:**
|
|
357
|
+
|
|
358
|
+
1. **Search/Filter**
|
|
359
|
+
- Real-time search across all theme properties
|
|
360
|
+
- Filter by property type (color, typography, spacing, etc.)
|
|
361
|
+
- Highlight matching results
|
|
362
|
+
- Search history
|
|
363
|
+
- Clear search button
|
|
364
|
+
|
|
365
|
+
2. **Copy Path Functionality**
|
|
366
|
+
- Click property names to copy dot-notation path
|
|
367
|
+
- Visual feedback on copy
|
|
368
|
+
- Copy button for each property
|
|
369
|
+
- Path format: `palette.primary.main`
|
|
370
|
+
- Toast notification on successful copy
|
|
371
|
+
|
|
372
|
+
**Implementation Details:**
|
|
373
|
+
- Debounced search input
|
|
374
|
+
- Recursive property path generation
|
|
375
|
+
- Clipboard API with fallback
|
|
376
|
+
- Highlight matching text in results
|
|
377
|
+
|
|
378
|
+
### Phase 4: Enhanced ThemeComparator
|
|
379
|
+
|
|
380
|
+
**New Features:**
|
|
381
|
+
|
|
382
|
+
1. **Search/Filter**
|
|
383
|
+
- Filter differences by type (added/removed/changed)
|
|
384
|
+
- Search within difference paths
|
|
385
|
+
- Filter by property category
|
|
386
|
+
- Clear filters button
|
|
387
|
+
|
|
388
|
+
2. **Improved Visual Diff**
|
|
389
|
+
- Green background for added properties
|
|
390
|
+
- Red background for removed properties
|
|
391
|
+
- Yellow/orange background for changed properties
|
|
392
|
+
- Side-by-side value highlighting
|
|
393
|
+
- Diff statistics with breakdown
|
|
394
|
+
|
|
395
|
+
**Implementation Details:**
|
|
396
|
+
- Filter state management
|
|
397
|
+
- Enhanced diff styling with better contrast
|
|
398
|
+
- Improved value comparison visualization
|
|
399
|
+
- Category-based filtering
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Implementation Status
|
|
404
|
+
|
|
405
|
+
### Completed ✅
|
|
406
|
+
- [x] IMPROVEMENTS.md documentation
|
|
407
|
+
- [x] useHistory hook implementation (`src/lib/theme/devtools/useHistory.ts`)
|
|
408
|
+
- [x] LiveEditor enhancements (undo/redo, keyboard shortcuts, resizable layout, enhanced color pickers)
|
|
409
|
+
- [x] Preview enhancements (interactive components, viewport controls, dark mode toggle)
|
|
410
|
+
- [x] Inspector enhancements (search/filter, copy path functionality)
|
|
411
|
+
- [x] Comparator enhancements (search/filter, improved visual diff styling)
|
|
412
|
+
- [x] CLI enhancements (list, inspect, compare, export commands)
|
|
413
|
+
|
|
414
|
+
### Remaining Tasks 🚧
|
|
415
|
+
- [ ] Testing and validation
|
|
416
|
+
- [ ] Storybook stories for new features
|
|
417
|
+
- [ ] Performance optimization
|
|
418
|
+
- [ ] Accessibility audit
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
321
422
|
## Files Changed
|
|
322
423
|
|
|
323
424
|
### Modified Files
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Theme Inspector Component
|
|
3
3
|
*
|
|
4
4
|
* React component for inspecting and debugging themes
|
|
5
|
+
* Enhanced with search/filter and copy path functionality
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import React, { useState, useMemo } from 'react';
|
|
8
|
+
import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
|
8
9
|
import type { Theme } from '../types';
|
|
9
|
-
import { generateCSSVariables } from '../generateCSSVariables';
|
|
10
|
-
import { ThemeValidator } from '
|
|
10
|
+
import { generateCSSVariables } from '../generators/generateCSSVariables';
|
|
11
|
+
import { ThemeValidator } from './ThemeValidator';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Theme inspector props
|
|
@@ -27,6 +28,15 @@ export interface ThemeInspectorProps {
|
|
|
27
28
|
style?: React.CSSProperties;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Property path information
|
|
33
|
+
*/
|
|
34
|
+
interface PropertyPath {
|
|
35
|
+
path: string;
|
|
36
|
+
value: any;
|
|
37
|
+
matches: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
/**
|
|
31
41
|
* Theme Inspector Component
|
|
32
42
|
*
|
|
@@ -42,6 +52,27 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
42
52
|
}) => {
|
|
43
53
|
const [activeTab, setActiveTab] = useState<'overview' | 'validation' | 'css' | 'structure'>('overview');
|
|
44
54
|
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set(['palette']));
|
|
55
|
+
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
56
|
+
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState<string>('');
|
|
57
|
+
const [copiedPath, setCopiedPath] = useState<string | null>(null);
|
|
58
|
+
const searchTimeoutRef = useRef<NodeJS.Timeout>();
|
|
59
|
+
|
|
60
|
+
// Debounce search query
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (searchTimeoutRef.current) {
|
|
63
|
+
clearTimeout(searchTimeoutRef.current);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
searchTimeoutRef.current = setTimeout(() => {
|
|
67
|
+
setDebouncedSearchQuery(searchQuery);
|
|
68
|
+
}, 300);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
if (searchTimeoutRef.current) {
|
|
72
|
+
clearTimeout(searchTimeoutRef.current);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}, [searchQuery]);
|
|
45
76
|
|
|
46
77
|
// Validation results
|
|
47
78
|
const validationResult = useMemo(() => {
|
|
@@ -74,6 +105,52 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
74
105
|
}
|
|
75
106
|
}, [theme, showCSSVariables]);
|
|
76
107
|
|
|
108
|
+
// Generate all property paths for search
|
|
109
|
+
const allPropertyPaths = useMemo(() => {
|
|
110
|
+
const paths: PropertyPath[] = [];
|
|
111
|
+
|
|
112
|
+
const traverse = (obj: any, path: string = '', depth: number = 0): void => {
|
|
113
|
+
if (depth > 10) return; // Prevent infinite recursion
|
|
114
|
+
|
|
115
|
+
if (obj === null || obj === undefined) {
|
|
116
|
+
paths.push({ path, value: obj, matches: false });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof obj === 'object' && !Array.isArray(obj)) {
|
|
121
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
122
|
+
if (key === '__isJSTheme') return;
|
|
123
|
+
|
|
124
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
125
|
+
const pathLower = currentPath.toLowerCase();
|
|
126
|
+
const queryLower = debouncedSearchQuery.toLowerCase();
|
|
127
|
+
const matches = debouncedSearchQuery ? pathLower.includes(queryLower) : true;
|
|
128
|
+
|
|
129
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
130
|
+
paths.push({ path: currentPath, value: null, matches });
|
|
131
|
+
traverse(value, currentPath, depth + 1);
|
|
132
|
+
} else {
|
|
133
|
+
const valueStr = typeof value === 'string' ? value.toLowerCase() : String(value).toLowerCase();
|
|
134
|
+
const valueMatches = debouncedSearchQuery ? valueStr.includes(queryLower) : true;
|
|
135
|
+
paths.push({
|
|
136
|
+
path: currentPath,
|
|
137
|
+
value,
|
|
138
|
+
matches: matches || valueMatches
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
const pathLower = path.toLowerCase();
|
|
144
|
+
const queryLower = debouncedSearchQuery.toLowerCase();
|
|
145
|
+
const matches = debouncedSearchQuery ? pathLower.includes(queryLower) : true;
|
|
146
|
+
paths.push({ path, value: obj, matches });
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
traverse(theme);
|
|
151
|
+
return paths;
|
|
152
|
+
}, [theme, debouncedSearchQuery]);
|
|
153
|
+
|
|
77
154
|
const toggleSection = (section: string) => {
|
|
78
155
|
const newExpanded = new Set(expandedSections);
|
|
79
156
|
if (newExpanded.has(section)) {
|
|
@@ -84,13 +161,50 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
84
161
|
setExpandedSections(newExpanded);
|
|
85
162
|
};
|
|
86
163
|
|
|
87
|
-
const
|
|
164
|
+
const copyPath = useCallback(async (path: string) => {
|
|
165
|
+
try {
|
|
166
|
+
await navigator.clipboard.writeText(path);
|
|
167
|
+
setCopiedPath(path);
|
|
168
|
+
setTimeout(() => setCopiedPath(null), 2000);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
// Fallback for older browsers
|
|
171
|
+
const textArea = document.createElement('textarea');
|
|
172
|
+
textArea.value = path;
|
|
173
|
+
textArea.style.position = 'fixed';
|
|
174
|
+
textArea.style.opacity = '0';
|
|
175
|
+
document.body.appendChild(textArea);
|
|
176
|
+
textArea.select();
|
|
177
|
+
try {
|
|
178
|
+
document.execCommand('copy');
|
|
179
|
+
setCopiedPath(path);
|
|
180
|
+
setTimeout(() => setCopiedPath(null), 2000);
|
|
181
|
+
} catch {
|
|
182
|
+
// Copy failed
|
|
183
|
+
}
|
|
184
|
+
document.body.removeChild(textArea);
|
|
185
|
+
}
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
const highlightText = (text: string, query: string): React.ReactNode => {
|
|
189
|
+
if (!query) return text;
|
|
190
|
+
|
|
191
|
+
const parts = text.split(new RegExp(`(${query})`, 'gi'));
|
|
192
|
+
return parts.map((part, index) =>
|
|
193
|
+
part.toLowerCase() === query.toLowerCase() ? (
|
|
194
|
+
<mark key={index} className="search-highlight">{part}</mark>
|
|
195
|
+
) : (
|
|
196
|
+
part
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const renderValue = (value: any, depth = 0, path = ''): React.ReactNode => {
|
|
88
202
|
if (value === null || value === undefined) {
|
|
89
203
|
return <span className="value-null">null</span>;
|
|
90
204
|
}
|
|
91
205
|
|
|
92
206
|
if (typeof value === 'string') {
|
|
93
|
-
return <span className="value-string">"{value}"</span>;
|
|
207
|
+
return <span className="value-string">"{highlightText(value, debouncedSearchQuery)}"</span>;
|
|
94
208
|
}
|
|
95
209
|
|
|
96
210
|
if (typeof value === 'number') {
|
|
@@ -110,7 +224,7 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
110
224
|
<div className="value-array">
|
|
111
225
|
[{value.map((item, index) => (
|
|
112
226
|
<div key={index} className="array-item">
|
|
113
|
-
{renderValue(item, depth + 1)}
|
|
227
|
+
{renderValue(item, depth + 1, `${path}[${index}]`)}
|
|
114
228
|
{index < value.length - 1 && ','}
|
|
115
229
|
</div>
|
|
116
230
|
))}]
|
|
@@ -122,12 +236,24 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
122
236
|
return (
|
|
123
237
|
<div className="value-object" style={{ marginLeft: depth * 16 }}>
|
|
124
238
|
{'{'}
|
|
125
|
-
{Object.entries(value).map(([key, val]) =>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
|
|
239
|
+
{Object.entries(value).map(([key, val]) => {
|
|
240
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
241
|
+
return (
|
|
242
|
+
<div key={key} className="object-property">
|
|
243
|
+
<span
|
|
244
|
+
className="property-key clickable"
|
|
245
|
+
onClick={() => copyPath(currentPath)}
|
|
246
|
+
title={`Click to copy: ${currentPath}`}
|
|
247
|
+
>
|
|
248
|
+
{highlightText(key, debouncedSearchQuery)}:
|
|
249
|
+
</span>{' '}
|
|
250
|
+
{renderValue(val, depth + 1, currentPath)}
|
|
251
|
+
{copiedPath === currentPath && (
|
|
252
|
+
<span className="copy-feedback">✓ Copied!</span>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
})}
|
|
131
257
|
{'}'}
|
|
132
258
|
</div>
|
|
133
259
|
);
|
|
@@ -282,48 +408,103 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
282
408
|
</div>
|
|
283
409
|
);
|
|
284
410
|
|
|
285
|
-
const renderStructure = () =>
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
411
|
+
const renderStructure = () => {
|
|
412
|
+
const filteredPaths = debouncedSearchQuery
|
|
413
|
+
? allPropertyPaths.filter(p => p.matches)
|
|
414
|
+
: allPropertyPaths;
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<div className="inspector-structure">
|
|
418
|
+
<div className="structure-header-controls">
|
|
419
|
+
<h3>Theme Structure</h3>
|
|
420
|
+
<div className="search-controls">
|
|
421
|
+
<input
|
|
422
|
+
type="text"
|
|
423
|
+
placeholder="Search properties..."
|
|
424
|
+
value={searchQuery}
|
|
425
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
426
|
+
className="search-input"
|
|
427
|
+
/>
|
|
428
|
+
{searchQuery && (
|
|
429
|
+
<button
|
|
430
|
+
className="clear-search"
|
|
431
|
+
onClick={() => setSearchQuery('')}
|
|
432
|
+
title="Clear search"
|
|
300
433
|
>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
434
|
+
×
|
|
435
|
+
</button>
|
|
436
|
+
)}
|
|
437
|
+
{debouncedSearchQuery && (
|
|
438
|
+
<span className="search-results-count">
|
|
439
|
+
{filteredPaths.length} result{filteredPaths.length !== 1 ? 's' : ''}
|
|
440
|
+
</span>
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
{debouncedSearchQuery && filteredPaths.length === 0 && (
|
|
446
|
+
<div className="no-results">
|
|
447
|
+
No properties found matching "{debouncedSearchQuery}"
|
|
448
|
+
</div>
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
<div className="structure-tree">
|
|
452
|
+
{Object.entries(theme).map(([key, value]) => {
|
|
453
|
+
if (key === '__isJSTheme') return null;
|
|
454
|
+
|
|
455
|
+
const isExpanded = expandedSections.has(key);
|
|
456
|
+
const hasChildren = typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
457
|
+
const pathMatches = debouncedSearchQuery
|
|
458
|
+
? allPropertyPaths.some(p => p.path.startsWith(key) && p.matches)
|
|
459
|
+
: true;
|
|
460
|
+
|
|
461
|
+
if (debouncedSearchQuery && !pathMatches) return null;
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<div key={key} className="structure-node">
|
|
465
|
+
<div
|
|
466
|
+
className="structure-header"
|
|
467
|
+
onClick={() => hasChildren && toggleSection(key)}
|
|
468
|
+
>
|
|
469
|
+
{hasChildren && (
|
|
470
|
+
<span className={`expand-icon ${isExpanded ? 'expanded' : ''}`}>
|
|
471
|
+
▶
|
|
472
|
+
</span>
|
|
473
|
+
)}
|
|
474
|
+
<span
|
|
475
|
+
className="property-name clickable"
|
|
476
|
+
onClick={(e) => {
|
|
477
|
+
e.stopPropagation();
|
|
478
|
+
copyPath(key);
|
|
479
|
+
}}
|
|
480
|
+
title={`Click to copy: ${key}`}
|
|
481
|
+
>
|
|
482
|
+
{highlightText(key, debouncedSearchQuery)}
|
|
483
|
+
</span>
|
|
484
|
+
{copiedPath === key && (
|
|
485
|
+
<span className="copy-feedback">✓ Copied!</span>
|
|
486
|
+
)}
|
|
487
|
+
<span className="property-type">
|
|
488
|
+
{Array.isArray(value) ? 'array' : typeof value}
|
|
304
489
|
</span>
|
|
490
|
+
</div>
|
|
491
|
+
{hasChildren && isExpanded && (
|
|
492
|
+
<div className="structure-children">
|
|
493
|
+
{renderValue(value, 0, key)}
|
|
494
|
+
</div>
|
|
495
|
+
)}
|
|
496
|
+
{!hasChildren && (
|
|
497
|
+
<div className="structure-value">
|
|
498
|
+
{renderValue(value, 0, key)}
|
|
499
|
+
</div>
|
|
305
500
|
)}
|
|
306
|
-
<span className="property-name">{key}</span>
|
|
307
|
-
<span className="property-type">
|
|
308
|
-
{Array.isArray(value) ? 'array' : typeof value}
|
|
309
|
-
</span>
|
|
310
501
|
</div>
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
</div>
|
|
315
|
-
)}
|
|
316
|
-
{!hasChildren && (
|
|
317
|
-
<div className="structure-value">
|
|
318
|
-
{renderValue(value)}
|
|
319
|
-
</div>
|
|
320
|
-
)}
|
|
321
|
-
</div>
|
|
322
|
-
);
|
|
323
|
-
})}
|
|
502
|
+
);
|
|
503
|
+
})}
|
|
504
|
+
</div>
|
|
324
505
|
</div>
|
|
325
|
-
|
|
326
|
-
|
|
506
|
+
);
|
|
507
|
+
};
|
|
327
508
|
|
|
328
509
|
return (
|
|
329
510
|
<div className={`atomix-theme-inspector ${className || ''}`} style={style}>
|
|
@@ -372,7 +553,7 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
372
553
|
{activeTab === 'structure' && renderStructure()}
|
|
373
554
|
</div>
|
|
374
555
|
|
|
375
|
-
<style
|
|
556
|
+
<style>{`
|
|
376
557
|
.atomix-theme-inspector {
|
|
377
558
|
border: 1px solid #e0e0e0;
|
|
378
559
|
border-radius: 8px;
|
|
@@ -529,6 +710,10 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
529
710
|
cursor: pointer;
|
|
530
711
|
}
|
|
531
712
|
|
|
713
|
+
.copy-button:hover {
|
|
714
|
+
background: #1976d2;
|
|
715
|
+
}
|
|
716
|
+
|
|
532
717
|
.css-code {
|
|
533
718
|
background: #f5f5f5;
|
|
534
719
|
padding: 16px;
|
|
@@ -539,6 +724,78 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
539
724
|
line-height: 1.4;
|
|
540
725
|
}
|
|
541
726
|
|
|
727
|
+
.structure-header-controls {
|
|
728
|
+
display: flex;
|
|
729
|
+
justify-content: space-between;
|
|
730
|
+
align-items: center;
|
|
731
|
+
margin-bottom: 16px;
|
|
732
|
+
flex-wrap: wrap;
|
|
733
|
+
gap: 12px;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.structure-header-controls h3 {
|
|
737
|
+
margin: 0;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.search-controls {
|
|
741
|
+
display: flex;
|
|
742
|
+
align-items: center;
|
|
743
|
+
gap: 8px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.search-input {
|
|
747
|
+
padding: 8px 12px;
|
|
748
|
+
border: 1px solid #e0e0e0;
|
|
749
|
+
border-radius: 4px;
|
|
750
|
+
font-size: 14px;
|
|
751
|
+
width: 250px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.search-input:focus {
|
|
755
|
+
outline: none;
|
|
756
|
+
border-color: #2196f3;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.clear-search {
|
|
760
|
+
background: #f44336;
|
|
761
|
+
color: white;
|
|
762
|
+
border: none;
|
|
763
|
+
border-radius: 50%;
|
|
764
|
+
width: 24px;
|
|
765
|
+
height: 24px;
|
|
766
|
+
cursor: pointer;
|
|
767
|
+
font-size: 18px;
|
|
768
|
+
line-height: 1;
|
|
769
|
+
display: flex;
|
|
770
|
+
align-items: center;
|
|
771
|
+
justify-content: center;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.clear-search:hover {
|
|
775
|
+
background: #d32f2f;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.search-results-count {
|
|
779
|
+
font-size: 12px;
|
|
780
|
+
color: #666;
|
|
781
|
+
padding: 4px 8px;
|
|
782
|
+
background: #f5f5f5;
|
|
783
|
+
border-radius: 4px;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.no-results {
|
|
787
|
+
padding: 24px;
|
|
788
|
+
text-align: center;
|
|
789
|
+
color: #666;
|
|
790
|
+
font-style: italic;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.search-highlight {
|
|
794
|
+
background: #fff59d;
|
|
795
|
+
padding: 2px 4px;
|
|
796
|
+
border-radius: 2px;
|
|
797
|
+
}
|
|
798
|
+
|
|
542
799
|
.structure-node {
|
|
543
800
|
margin-bottom: 8px;
|
|
544
801
|
}
|
|
@@ -570,6 +827,23 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
570
827
|
color: #1976d2;
|
|
571
828
|
}
|
|
572
829
|
|
|
830
|
+
.property-name.clickable {
|
|
831
|
+
cursor: pointer;
|
|
832
|
+
text-decoration: underline;
|
|
833
|
+
text-decoration-style: dotted;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.property-name.clickable:hover {
|
|
837
|
+
color: #0d47a1;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.copy-feedback {
|
|
841
|
+
font-size: 12px;
|
|
842
|
+
color: #4caf50;
|
|
843
|
+
margin-left: 8px;
|
|
844
|
+
font-weight: normal;
|
|
845
|
+
}
|
|
846
|
+
|
|
573
847
|
.property-type {
|
|
574
848
|
font-size: 12px;
|
|
575
849
|
color: #666;
|
|
@@ -603,7 +877,17 @@ export const ThemeInspector: React.FC<ThemeInspectorProps> = ({
|
|
|
603
877
|
color: #1976d2;
|
|
604
878
|
font-weight: bold;
|
|
605
879
|
}
|
|
880
|
+
|
|
881
|
+
.property-key.clickable {
|
|
882
|
+
cursor: pointer;
|
|
883
|
+
text-decoration: underline;
|
|
884
|
+
text-decoration-style: dotted;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.property-key.clickable:hover {
|
|
888
|
+
color: #0d47a1;
|
|
889
|
+
}
|
|
606
890
|
`}</style>
|
|
607
891
|
</div>
|
|
608
892
|
);
|
|
609
|
-
};
|
|
893
|
+
};
|