@shohojdhara/atomix 0.4.0 → 0.4.2
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/dist/atomix.css +0 -14
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -359
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +98 -28
- package/dist/core.js +1082 -733
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +26 -21
- package/dist/forms.js +937 -350
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -256
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +518 -284
- package/dist/index.esm.js +1993 -1237
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1994 -1237
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +2 -2
- package/scripts/atomix-cli.js +43 -1
- package/scripts/cli/__tests__/utils.test.js +6 -2
- package/scripts/cli/migration-tools.js +2 -2
- package/scripts/cli/theme-bridge.js +7 -9
- package/scripts/cli/utils.js +2 -1
- package/src/components/Accordion/Accordion.stories.tsx +40 -0
- package/src/components/Accordion/Accordion.tsx +174 -56
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Callout/README.md +2 -2
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +24 -1
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +145 -94
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +39 -2
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +143 -4
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +16 -5
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +66 -2
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +125 -22
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +198 -45
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -8
- package/src/lib/composables/useAtomixGlass.ts +331 -537
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +2 -10
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +21 -23
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
|
@@ -66,7 +66,7 @@ const meta: Meta<typeof AtomixGlass> = {
|
|
|
66
66
|
description: 'Elasticity factor for mouse interactions (default: 0.15)',
|
|
67
67
|
table: { defaultValue: { summary: '0.15' } },
|
|
68
68
|
},
|
|
69
|
-
|
|
69
|
+
borderRadius: {
|
|
70
70
|
control: { type: 'range', min: 0, max: 50, step: 1 },
|
|
71
71
|
description: 'Corner radius in pixels (default: 20)',
|
|
72
72
|
table: { defaultValue: { summary: '20' } },
|
|
@@ -137,12 +137,12 @@ const meta: Meta<typeof AtomixGlass> = {
|
|
|
137
137
|
description: 'Override for high contrast preference (default: false)',
|
|
138
138
|
table: { defaultValue: { summary: 'false' } },
|
|
139
139
|
},
|
|
140
|
-
|
|
140
|
+
withoutEffects: {
|
|
141
141
|
control: 'boolean',
|
|
142
142
|
description: 'Disable all visual effects (default: false)',
|
|
143
143
|
table: { defaultValue: { summary: 'false' } },
|
|
144
144
|
},
|
|
145
|
-
|
|
145
|
+
debugPerformance: {
|
|
146
146
|
control: 'boolean',
|
|
147
147
|
description: 'Enable performance monitoring (default: false)',
|
|
148
148
|
table: { defaultValue: { summary: 'false' } },
|
|
@@ -299,13 +299,13 @@ export const Playground: Story = {
|
|
|
299
299
|
saturation: 140,
|
|
300
300
|
aberrationIntensity: 2,
|
|
301
301
|
elasticity: 0.15,
|
|
302
|
-
|
|
302
|
+
borderRadius: 20,
|
|
303
303
|
overLight: false,
|
|
304
304
|
reducedMotion: false,
|
|
305
305
|
highContrast: false,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
withoutEffects: false,
|
|
307
|
+
withLiquidBlur: false,
|
|
308
|
+
withBorder: true,
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -333,13 +333,13 @@ export const Playground: Story = {
|
|
|
333
333
|
saturation: 110,
|
|
334
334
|
aberrationIntensity: 0.5,
|
|
335
335
|
elasticity: 0.05,
|
|
336
|
-
|
|
336
|
+
borderRadius: 12,
|
|
337
337
|
overLight: false,
|
|
338
338
|
reducedMotion: false,
|
|
339
339
|
highContrast: false,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
withoutEffects: false,
|
|
341
|
+
withLiquidBlur: false,
|
|
342
|
+
withBorder: true,
|
|
343
343
|
},
|
|
344
344
|
mode: 'standard' as const,
|
|
345
345
|
shader: 'liquidGlass' as const,
|
|
@@ -353,13 +353,13 @@ export const Playground: Story = {
|
|
|
353
353
|
saturation: 140,
|
|
354
354
|
aberrationIntensity: 2,
|
|
355
355
|
elasticity: 0.15,
|
|
356
|
-
|
|
356
|
+
borderRadius: 20,
|
|
357
357
|
overLight: false,
|
|
358
358
|
reducedMotion: false,
|
|
359
359
|
highContrast: false,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
360
|
+
withoutEffects: false,
|
|
361
|
+
withLiquidBlur: false,
|
|
362
|
+
withBorder: true,
|
|
363
363
|
},
|
|
364
364
|
mode: 'standard' as const,
|
|
365
365
|
shader: 'liquidGlass' as const,
|
|
@@ -373,13 +373,13 @@ export const Playground: Story = {
|
|
|
373
373
|
saturation: 170,
|
|
374
374
|
aberrationIntensity: 3.5,
|
|
375
375
|
elasticity: 0.25,
|
|
376
|
-
|
|
376
|
+
borderRadius: 28,
|
|
377
377
|
overLight: false,
|
|
378
378
|
reducedMotion: false,
|
|
379
379
|
highContrast: false,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
380
|
+
withoutEffects: false,
|
|
381
|
+
withLiquidBlur: true,
|
|
382
|
+
withBorder: true,
|
|
383
383
|
},
|
|
384
384
|
mode: 'prominent' as const,
|
|
385
385
|
shader: 'plasma' as const,
|
|
@@ -393,13 +393,13 @@ export const Playground: Story = {
|
|
|
393
393
|
saturation: 200,
|
|
394
394
|
aberrationIntensity: 5,
|
|
395
395
|
elasticity: 0.35,
|
|
396
|
-
|
|
396
|
+
borderRadius: 32,
|
|
397
397
|
overLight: false,
|
|
398
398
|
reducedMotion: false,
|
|
399
399
|
highContrast: false,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
400
|
+
withoutEffects: false,
|
|
401
|
+
withLiquidBlur: true,
|
|
402
|
+
withBorder: true,
|
|
403
403
|
},
|
|
404
404
|
mode: 'shader' as const,
|
|
405
405
|
shader: 'waves' as const,
|
|
@@ -420,15 +420,15 @@ export const Playground: Story = {
|
|
|
420
420
|
saturation={${settings.saturation}}
|
|
421
421
|
aberrationIntensity={${settings.aberrationIntensity}}
|
|
422
422
|
elasticity={${settings.elasticity}}
|
|
423
|
-
|
|
423
|
+
borderRadius={${settings.borderRadius}}
|
|
424
424
|
overLight={${settings.overLight}}
|
|
425
425
|
mode="${selectedMode}"
|
|
426
426
|
shaderVariant="${selectedShader}"
|
|
427
427
|
reducedMotion={${settings.reducedMotion}}
|
|
428
428
|
highContrast={${settings.highContrast}}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
429
|
+
withoutEffects={${settings.withoutEffects}}
|
|
430
|
+
withLiquidBlur={${settings.withLiquidBlur}}
|
|
431
|
+
withBorder={${settings.withBorder}}
|
|
432
432
|
>
|
|
433
433
|
<div className="your-content">
|
|
434
434
|
{/* Your content here */}
|
|
@@ -729,7 +729,7 @@ export const Playground: Story = {
|
|
|
729
729
|
? 300
|
|
730
730
|
: key === 'aberrationIntensity'
|
|
731
731
|
? 10
|
|
732
|
-
: key === '
|
|
732
|
+
: key === 'borderRadius'
|
|
733
733
|
? 100
|
|
734
734
|
: key === 'blurAmount'
|
|
735
735
|
? 10
|
|
@@ -872,13 +872,13 @@ export const Playground: Story = {
|
|
|
872
872
|
saturation: 140,
|
|
873
873
|
aberrationIntensity: 2,
|
|
874
874
|
elasticity: 0.15,
|
|
875
|
-
|
|
875
|
+
borderRadius: 20,
|
|
876
876
|
overLight: false,
|
|
877
877
|
reducedMotion: false,
|
|
878
878
|
highContrast: false,
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
879
|
+
withoutEffects: false,
|
|
880
|
+
withLiquidBlur: false,
|
|
881
|
+
withBorder: true,
|
|
882
882
|
});
|
|
883
883
|
setSelectedMode('standard');
|
|
884
884
|
setSelectedShader('liquidGlass');
|
|
@@ -906,7 +906,7 @@ export const Playground: Story = {
|
|
|
906
906
|
<AtomixGlass
|
|
907
907
|
displacementScale={80}
|
|
908
908
|
aberrationIntensity={1}
|
|
909
|
-
|
|
909
|
+
borderRadius={16}
|
|
910
910
|
saturation={120}
|
|
911
911
|
>
|
|
912
912
|
<div style={{ padding: '2.5rem' }}>
|
|
@@ -980,15 +980,15 @@ export const Playground: Story = {
|
|
|
980
980
|
saturation={settings.saturation}
|
|
981
981
|
aberrationIntensity={settings.aberrationIntensity}
|
|
982
982
|
elasticity={settings.elasticity}
|
|
983
|
-
|
|
983
|
+
borderRadius={settings.borderRadius}
|
|
984
984
|
overLight={settings.overLight}
|
|
985
985
|
mode={selectedMode}
|
|
986
986
|
shaderVariant={selectedShader as any}
|
|
987
987
|
reducedMotion={settings.reducedMotion}
|
|
988
988
|
highContrast={settings.highContrast}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
989
|
+
withoutEffects={settings.withoutEffects}
|
|
990
|
+
withLiquidBlur={settings.withLiquidBlur}
|
|
991
|
+
withBorder={settings.withBorder}
|
|
992
992
|
style={{ width: '100%' }}
|
|
993
993
|
>
|
|
994
994
|
<div style={{ padding: '2.5rem', textAlign: 'center' }}>
|
|
@@ -1150,31 +1150,31 @@ export const Playground: Story = {
|
|
|
1150
1150
|
style={{
|
|
1151
1151
|
padding: '4px 12px',
|
|
1152
1152
|
borderRadius: '12px',
|
|
1153
|
-
background: settings.
|
|
1153
|
+
background: settings.withLiquidBlur
|
|
1154
1154
|
? 'rgba(122, 255, 215, 0.2)'
|
|
1155
1155
|
: 'rgba(255,255,255,0.1)',
|
|
1156
1156
|
fontSize: '0.75rem',
|
|
1157
|
-
border: settings.
|
|
1157
|
+
border: settings.withLiquidBlur
|
|
1158
1158
|
? '1px solid #7AFFD7'
|
|
1159
1159
|
: '1px solid transparent',
|
|
1160
1160
|
}}
|
|
1161
1161
|
>
|
|
1162
|
-
{settings.
|
|
1162
|
+
{settings.withLiquidBlur ? '✓' : '○'} Liquid Blur
|
|
1163
1163
|
</div>
|
|
1164
1164
|
<div
|
|
1165
1165
|
style={{
|
|
1166
1166
|
padding: '4px 12px',
|
|
1167
1167
|
borderRadius: '12px',
|
|
1168
|
-
background: settings.
|
|
1168
|
+
background: settings.withBorder
|
|
1169
1169
|
? 'rgba(122, 255, 215, 0.2)'
|
|
1170
1170
|
: 'rgba(255,255,255,0.1)',
|
|
1171
1171
|
fontSize: '0.75rem',
|
|
1172
|
-
border: settings.
|
|
1172
|
+
border: settings.withBorder
|
|
1173
1173
|
? '1px solid #7AFFD7'
|
|
1174
1174
|
: '1px solid transparent',
|
|
1175
1175
|
}}
|
|
1176
1176
|
>
|
|
1177
|
-
{settings.
|
|
1177
|
+
{settings.withBorder ? '✓' : '○'} Border Effect
|
|
1178
1178
|
</div>
|
|
1179
1179
|
<div
|
|
1180
1180
|
style={{
|
|
@@ -1238,7 +1238,7 @@ export const Playground: Story = {
|
|
|
1238
1238
|
<div>
|
|
1239
1239
|
<span className="u-opacity-70">Radius:</span>
|
|
1240
1240
|
<span className="u-font-semibold u-ml-2">
|
|
1241
|
-
{settings.
|
|
1241
|
+
{settings.borderRadius}px
|
|
1242
1242
|
</span>
|
|
1243
1243
|
</div>
|
|
1244
1244
|
<div>
|
|
@@ -1250,7 +1250,7 @@ export const Playground: Story = {
|
|
|
1250
1250
|
<div>
|
|
1251
1251
|
<span className="u-opacity-70">Effects:</span>
|
|
1252
1252
|
<span className="u-font-semibold u-ml-2">
|
|
1253
|
-
{settings.
|
|
1253
|
+
{settings.withoutEffects ? 'Disabled' : 'Enabled'}
|
|
1254
1254
|
</span>
|
|
1255
1255
|
</div>
|
|
1256
1256
|
</div>
|
|
@@ -125,7 +125,7 @@ export const LiquidGlass: Story = {
|
|
|
125
125
|
saturation: 150,
|
|
126
126
|
aberrationIntensity: 2,
|
|
127
127
|
elasticity: 0.2,
|
|
128
|
-
|
|
128
|
+
borderRadius: 32,
|
|
129
129
|
mode: 'shader',
|
|
130
130
|
shaderVariant: 'liquidGlass',
|
|
131
131
|
},
|
|
@@ -246,7 +246,7 @@ export const AppleFluid: Story = {
|
|
|
246
246
|
saturation: 150,
|
|
247
247
|
aberrationIntensity: 2,
|
|
248
248
|
elasticity: 0.2,
|
|
249
|
-
|
|
249
|
+
borderRadius: 32,
|
|
250
250
|
mode: 'shader',
|
|
251
251
|
shaderVariant: 'appleFluid',
|
|
252
252
|
},
|
|
@@ -367,7 +367,7 @@ export const PremiumGlass: Story = {
|
|
|
367
367
|
saturation: 150,
|
|
368
368
|
aberrationIntensity: 2,
|
|
369
369
|
elasticity: 0.2,
|
|
370
|
-
|
|
370
|
+
borderRadius: 32,
|
|
371
371
|
mode: 'shader',
|
|
372
372
|
shaderVariant: 'premiumGlass',
|
|
373
373
|
},
|
|
@@ -60,7 +60,7 @@ export const Badge: React.FC<BadgeProps> = memo(
|
|
|
60
60
|
// Default glass settings for badges
|
|
61
61
|
const defaultGlassProps = {
|
|
62
62
|
displacementScale: 20,
|
|
63
|
-
|
|
63
|
+
borderRadius: ref.current?.getBoundingClientRect().width
|
|
64
64
|
? ref.current?.getBoundingClientRect().width / 2
|
|
65
65
|
: 16,
|
|
66
66
|
className: 'c-badge--glass',
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
ReactNode,
|
|
3
|
+
memo,
|
|
4
|
+
forwardRef,
|
|
5
|
+
Children,
|
|
6
|
+
cloneElement,
|
|
7
|
+
isValidElement,
|
|
8
|
+
ElementType,
|
|
9
|
+
} from 'react';
|
|
2
10
|
import { BREADCRUMB } from '../../lib/constants/components';
|
|
3
11
|
|
|
4
|
-
|
|
12
|
+
// Legacy Item Interface
|
|
13
|
+
export interface BreadcrumbItemData {
|
|
5
14
|
/**
|
|
6
15
|
* Text to display
|
|
7
16
|
*/
|
|
@@ -38,11 +47,110 @@ export interface BreadcrumbItem {
|
|
|
38
47
|
className?: string;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
// Rename exported type to avoid conflict with the component constant
|
|
51
|
+
export type BreadcrumbItemType = BreadcrumbItemData;
|
|
52
|
+
|
|
53
|
+
// Compound Component Props
|
|
54
|
+
export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
|
55
|
+
/**
|
|
56
|
+
* URL for the breadcrumb item
|
|
57
|
+
*/
|
|
58
|
+
href?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether this item is active (current page)
|
|
62
|
+
*/
|
|
63
|
+
active?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Optional icon to display before the label
|
|
67
|
+
*/
|
|
68
|
+
icon?: ReactNode;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Optional click handler for the link
|
|
72
|
+
*/
|
|
73
|
+
onClick?: (event: React.MouseEvent<any>) => void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Optional custom link component
|
|
77
|
+
*/
|
|
78
|
+
linkAs?: React.ElementType<any>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Link props to pass to the underlying anchor or LinkComponent
|
|
82
|
+
*/
|
|
83
|
+
linkProps?: Record<string, any>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
|
|
87
|
+
(
|
|
88
|
+
{
|
|
89
|
+
children,
|
|
90
|
+
href,
|
|
91
|
+
active,
|
|
92
|
+
icon,
|
|
93
|
+
onClick,
|
|
94
|
+
className = '',
|
|
95
|
+
style,
|
|
96
|
+
linkAs,
|
|
97
|
+
linkProps = {},
|
|
98
|
+
...props
|
|
99
|
+
},
|
|
100
|
+
ref
|
|
101
|
+
) => {
|
|
102
|
+
const itemClasses = [
|
|
103
|
+
BREADCRUMB.CLASSES.ITEM,
|
|
104
|
+
active ? BREADCRUMB.CLASSES.ACTIVE : '',
|
|
105
|
+
className,
|
|
106
|
+
]
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.join(' ');
|
|
109
|
+
|
|
110
|
+
const linkContent = (
|
|
111
|
+
<>
|
|
112
|
+
{icon && <span className="c-breadcrumb__icon">{icon}</span>}
|
|
113
|
+
{children}
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const commonLinkProps = {
|
|
118
|
+
className: BREADCRUMB.CLASSES.LINK,
|
|
119
|
+
onClick: onClick as any,
|
|
120
|
+
style, // Apply style to the link as per legacy behavior
|
|
121
|
+
...linkProps,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const LinkComponent = linkAs;
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<li ref={ref} className={itemClasses} style={style} {...props}>
|
|
128
|
+
{href && !active ? (
|
|
129
|
+
LinkComponent ? (
|
|
130
|
+
// @ts-ignore - Dynamic components are tricky in TS without stricter types
|
|
131
|
+
<LinkComponent href={href} {...commonLinkProps}>
|
|
132
|
+
{linkContent}
|
|
133
|
+
</LinkComponent>
|
|
134
|
+
) : (
|
|
135
|
+
<a href={href} {...(commonLinkProps as React.HTMLAttributes<HTMLAnchorElement>)}>
|
|
136
|
+
{linkContent}
|
|
137
|
+
</a>
|
|
138
|
+
)
|
|
139
|
+
) : (
|
|
140
|
+
<span className={BREADCRUMB.CLASSES.LINK}>{linkContent}</span>
|
|
141
|
+
)}
|
|
142
|
+
</li>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
BreadcrumbItem.displayName = 'BreadcrumbItem';
|
|
148
|
+
|
|
41
149
|
export interface BreadcrumbProps {
|
|
42
150
|
/**
|
|
43
|
-
* Array of breadcrumb items
|
|
151
|
+
* Array of breadcrumb items (Legacy)
|
|
44
152
|
*/
|
|
45
|
-
items
|
|
153
|
+
items?: BreadcrumbItemData[];
|
|
46
154
|
|
|
47
155
|
/**
|
|
48
156
|
* Custom divider character or element
|
|
@@ -68,71 +176,83 @@ export interface BreadcrumbProps {
|
|
|
68
176
|
* Custom style for the breadcrumb
|
|
69
177
|
*/
|
|
70
178
|
style?: React.CSSProperties;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Children (Compound)
|
|
182
|
+
*/
|
|
183
|
+
children?: ReactNode;
|
|
71
184
|
}
|
|
72
|
-
export const Breadcrumb: React.FC<BreadcrumbProps> = memo(
|
|
73
|
-
({
|
|
74
|
-
items,
|
|
75
|
-
divider,
|
|
76
|
-
className = '',
|
|
77
|
-
'aria-label': ariaLabel = 'Breadcrumb',
|
|
78
|
-
LinkComponent,
|
|
79
|
-
style,
|
|
80
|
-
}) => {
|
|
81
|
-
const breadcrumbClasses = [BREADCRUMB.CLASSES.BASE, className].filter(Boolean).join(' ');
|
|
82
185
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
186
|
+
const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbBase({
|
|
187
|
+
items,
|
|
188
|
+
divider,
|
|
189
|
+
className = '',
|
|
190
|
+
'aria-label': ariaLabel = 'Breadcrumb',
|
|
191
|
+
LinkComponent,
|
|
192
|
+
style,
|
|
193
|
+
children,
|
|
194
|
+
}: BreadcrumbProps) {
|
|
195
|
+
const breadcrumbClasses = [BREADCRUMB.CLASSES.BASE, className].filter(Boolean).join(' ');
|
|
196
|
+
|
|
197
|
+
let content: ReactNode;
|
|
198
|
+
|
|
199
|
+
if (items && items.length > 0) {
|
|
200
|
+
// Legacy rendering
|
|
201
|
+
content = items.map((item: BreadcrumbItemData, index: number) => {
|
|
202
|
+
const isLast = index === items.length - 1;
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<BreadcrumbItem
|
|
206
|
+
key={index}
|
|
207
|
+
href={item.href}
|
|
208
|
+
active={item.active || isLast}
|
|
209
|
+
icon={item.icon}
|
|
210
|
+
onClick={item.onClick as any}
|
|
211
|
+
className={item.className}
|
|
212
|
+
style={item.style}
|
|
213
|
+
linkAs={LinkComponent}
|
|
214
|
+
>
|
|
215
|
+
{item.label}
|
|
216
|
+
</BreadcrumbItem>
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
} else {
|
|
220
|
+
// Compound rendering
|
|
221
|
+
const childrenCount = Children.count(children);
|
|
222
|
+
content = Children.map(children, (child, index) => {
|
|
223
|
+
if (isValidElement(child)) {
|
|
224
|
+
const isLast = index === childrenCount - 1;
|
|
225
|
+
const childProps = child.props as any;
|
|
226
|
+
|
|
227
|
+
// Extract props from the child element
|
|
228
|
+
const { active, linkAs, ...otherProps } = childProps;
|
|
229
|
+
|
|
230
|
+
const newProps = {
|
|
231
|
+
active: active ?? (isLast ? true : undefined),
|
|
232
|
+
linkAs: linkAs ?? LinkComponent,
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return cloneElement(child, newProps as any);
|
|
236
|
+
}
|
|
237
|
+
return child;
|
|
238
|
+
});
|
|
133
239
|
}
|
|
134
|
-
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<nav aria-label={ariaLabel} style={style}>
|
|
243
|
+
<ol className={breadcrumbClasses}>{content}</ol>
|
|
244
|
+
</nav>
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
export type BreadcrumbType = typeof BreadcrumbComponent & {
|
|
249
|
+
Item: typeof BreadcrumbItem;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const Breadcrumb = BreadcrumbComponent as BreadcrumbType;
|
|
135
253
|
|
|
136
254
|
Breadcrumb.displayName = 'Breadcrumb';
|
|
255
|
+
Breadcrumb.Item = BreadcrumbItem;
|
|
137
256
|
|
|
138
257
|
export default Breadcrumb;
|
|
258
|
+
export type { BreadcrumbItemData as BreadcrumbItemLegacy };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { Breadcrumb } from './Breadcrumb';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
describe('Breadcrumb Component', () => {
|
|
7
|
+
it('renders correctly with legacy items prop', () => {
|
|
8
|
+
const items = [
|
|
9
|
+
{ label: 'Home', href: '/' },
|
|
10
|
+
{ label: 'Products', href: '/products' },
|
|
11
|
+
{ label: 'Current', active: true },
|
|
12
|
+
];
|
|
13
|
+
render(<Breadcrumb items={items} />);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByText('Home').closest('a')).toHaveAttribute('href', '/');
|
|
16
|
+
expect(screen.getByText('Products').closest('a')).toHaveAttribute('href', '/products');
|
|
17
|
+
expect(screen.getByText('Current').closest('span')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders correctly with compound components', () => {
|
|
21
|
+
render(
|
|
22
|
+
<Breadcrumb>
|
|
23
|
+
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
|
|
24
|
+
<Breadcrumb.Item href="/products">Products</Breadcrumb.Item>
|
|
25
|
+
<Breadcrumb.Item active>Current</Breadcrumb.Item>
|
|
26
|
+
</Breadcrumb>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByText('Home').closest('a')).toHaveAttribute('href', '/');
|
|
30
|
+
expect(screen.getByText('Products').closest('a')).toHaveAttribute('href', '/products');
|
|
31
|
+
expect(screen.getByText('Current').closest('span')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles click events in compound components', () => {
|
|
35
|
+
const handleClick = vi.fn();
|
|
36
|
+
render(
|
|
37
|
+
<Breadcrumb>
|
|
38
|
+
<Breadcrumb.Item href="#" onClick={handleClick} active={false}>
|
|
39
|
+
Click Me
|
|
40
|
+
</Breadcrumb.Item>
|
|
41
|
+
<Breadcrumb.Item>Current</Breadcrumb.Item>
|
|
42
|
+
</Breadcrumb>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
fireEvent.click(screen.getByText('Click Me'));
|
|
46
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('supports custom LinkComponent in compound components', () => {
|
|
50
|
+
const CustomLink = ({ href, children, ...props }: any) => (
|
|
51
|
+
<a href={href} data-testid="custom-link" {...props}>
|
|
52
|
+
{children} (Custom)
|
|
53
|
+
</a>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
render(
|
|
57
|
+
<Breadcrumb LinkComponent={CustomLink}>
|
|
58
|
+
<Breadcrumb.Item href="/custom">Link</Breadcrumb.Item>
|
|
59
|
+
<Breadcrumb.Item>Current</Breadcrumb.Item>
|
|
60
|
+
</Breadcrumb>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const link = screen.getByTestId('custom-link');
|
|
64
|
+
expect(link).toHaveAttribute('href', '/custom');
|
|
65
|
+
expect(link).toHaveTextContent('Link (Custom)');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('supports explicit linkAs prop on Item', () => {
|
|
69
|
+
const CustomLink = ({ href, children, ...props }: any) => (
|
|
70
|
+
<a href={href} data-testid="item-custom-link" {...props}>
|
|
71
|
+
{children}
|
|
72
|
+
</a>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<Breadcrumb>
|
|
77
|
+
<Breadcrumb.Item href="/explicit" linkAs={CustomLink} active={false}>Explicit</Breadcrumb.Item>
|
|
78
|
+
<Breadcrumb.Item>Current</Breadcrumb.Item>
|
|
79
|
+
</Breadcrumb>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(screen.getByTestId('item-custom-link')).toHaveAttribute('href', '/explicit');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Breadcrumb } from './Breadcrumb';
|
|
2
1
|
export { default } from './Breadcrumb';
|
|
3
|
-
export type { BreadcrumbProps,
|
|
2
|
+
export type { BreadcrumbProps, BreadcrumbItemProps, BreadcrumbType } from './Breadcrumb';
|
|
3
|
+
export type { BreadcrumbItemLegacy as BreadcrumbItem } from './Breadcrumb';
|