@udixio/ui-react 2.9.5 → 2.9.7

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 (52) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.js +3436 -2702
  4. package/dist/lib/components/Chip.d.ts.map +1 -1
  5. package/dist/lib/components/Chips.d.ts.map +1 -1
  6. package/dist/lib/components/Tab.d.ts +1 -1
  7. package/dist/lib/components/Tab.d.ts.map +1 -1
  8. package/dist/lib/components/TabGroup.d.ts +9 -0
  9. package/dist/lib/components/TabGroup.d.ts.map +1 -0
  10. package/dist/lib/components/TabGroupContext.d.ts +10 -0
  11. package/dist/lib/components/TabGroupContext.d.ts.map +1 -0
  12. package/dist/lib/components/TabPanel.d.ts +10 -0
  13. package/dist/lib/components/TabPanel.d.ts.map +1 -0
  14. package/dist/lib/components/TabPanels.d.ts +10 -0
  15. package/dist/lib/components/TabPanels.d.ts.map +1 -0
  16. package/dist/lib/components/Tabs.d.ts.map +1 -1
  17. package/dist/lib/components/index.d.ts +3 -0
  18. package/dist/lib/components/index.d.ts.map +1 -1
  19. package/dist/lib/effects/smooth-scroll.effect.d.ts +35 -11
  20. package/dist/lib/effects/smooth-scroll.effect.d.ts.map +1 -1
  21. package/dist/lib/interfaces/index.d.ts +2 -0
  22. package/dist/lib/interfaces/index.d.ts.map +1 -1
  23. package/dist/lib/interfaces/tab-group.interface.d.ts +13 -0
  24. package/dist/lib/interfaces/tab-group.interface.d.ts.map +1 -0
  25. package/dist/lib/interfaces/tab-panels.interface.d.ts +20 -0
  26. package/dist/lib/interfaces/tab-panels.interface.d.ts.map +1 -0
  27. package/dist/lib/interfaces/tab.interface.d.ts +2 -1
  28. package/dist/lib/interfaces/tab.interface.d.ts.map +1 -1
  29. package/dist/lib/styles/index.d.ts +1 -0
  30. package/dist/lib/styles/index.d.ts.map +1 -1
  31. package/dist/lib/styles/tab-panels.style.d.ts +31 -0
  32. package/dist/lib/styles/tab-panels.style.d.ts.map +1 -0
  33. package/dist/lib/styles/tab.style.d.ts +2 -0
  34. package/dist/lib/styles/tab.style.d.ts.map +1 -1
  35. package/package.json +4 -3
  36. package/src/lib/components/Chip.tsx +2 -1
  37. package/src/lib/components/Chips.tsx +57 -16
  38. package/src/lib/components/Tab.tsx +5 -1
  39. package/src/lib/components/TabGroup.tsx +60 -0
  40. package/src/lib/components/TabGroupContext.tsx +11 -0
  41. package/src/lib/components/TabPanel.tsx +24 -0
  42. package/src/lib/components/TabPanels.tsx +71 -0
  43. package/src/lib/components/Tabs.tsx +17 -6
  44. package/src/lib/components/index.ts +3 -0
  45. package/src/lib/effects/smooth-scroll.effect.tsx +81 -125
  46. package/src/lib/interfaces/index.ts +2 -0
  47. package/src/lib/interfaces/tab-group.interface.ts +13 -0
  48. package/src/lib/interfaces/tab-panels.interface.ts +21 -0
  49. package/src/lib/interfaces/tab.interface.ts +2 -1
  50. package/src/lib/styles/index.ts +1 -0
  51. package/src/lib/styles/tab-panels.style.ts +35 -0
  52. package/src/stories/effect/smooth-scroll.stories.tsx +34 -25
@@ -0,0 +1,71 @@
1
+ import React, { useContext } from 'react';
2
+ import { AnimatePresence, motion } from 'motion/react';
3
+ import { TabGroupContext } from './TabGroupContext';
4
+ import { TabPanelsInterface } from '../interfaces/tab-panels.interface';
5
+ import { ReactProps } from '../utils/component';
6
+ import { useTabPanelsStyle } from '../styles/tab-panels.style';
7
+ import { TabPanel } from './TabPanel';
8
+
9
+ /**
10
+ * TabPanels renders the content panels with slide animation
11
+ * Must be used within a TabGroup
12
+ * @status beta
13
+ * @category Navigation
14
+ */
15
+ export const TabPanels = ({
16
+ children,
17
+ className,
18
+ }: ReactProps<TabPanelsInterface>) => {
19
+ const context = useContext(TabGroupContext);
20
+
21
+ if (!context) {
22
+ console.warn('TabPanels must be used within a TabGroup');
23
+ return null;
24
+ }
25
+
26
+ const { selectedTab, direction, tabsId } = context;
27
+
28
+ const panelChildren = React.Children.toArray(children).filter(
29
+ (child) => React.isValidElement(child) && child.type === TabPanel,
30
+ ) as React.ReactElement[];
31
+
32
+ const styles = useTabPanelsStyle({
33
+ children,
34
+ className,
35
+ });
36
+
37
+ return (
38
+ <div className={styles.tabPanels}>
39
+ <AnimatePresence initial={false} custom={direction} mode="popLayout">
40
+ {panelChildren.map(
41
+ (child, index) =>
42
+ selectedTab === index && (
43
+ <motion.div
44
+ key={index}
45
+ custom={direction}
46
+ variants={{
47
+ enter: (dir: number) => ({
48
+ x: dir * 100 + '%',
49
+ opacity: 1,
50
+ }),
51
+ center: { x: 0, opacity: 1 },
52
+ exit: (dir: number) => ({
53
+ x: dir * -100 + '%',
54
+ opacity: 1,
55
+ }),
56
+ }}
57
+ initial="enter"
58
+ animate="center"
59
+ exit="exit"
60
+ transition={{ type: 'spring', stiffness: 400, damping: 40 }}
61
+ role="tabpanel"
62
+ aria-labelledby={`tab-${tabsId}-${index}`}
63
+ >
64
+ {React.cloneElement(child, { isSelected: true })}
65
+ </motion.div>
66
+ ),
67
+ )}
68
+ </AnimatePresence>
69
+ </div>
70
+ );
71
+ };
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useContext, useMemo, useState } from 'react';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { TabsInterface } from '../interfaces/tabs.interface';
4
4
 
@@ -6,6 +6,7 @@ import { useTabsStyle } from '../styles/tabs.style';
6
6
  import { ReactProps } from '../utils/component';
7
7
  import { TabProps } from '../interfaces/tab.interface';
8
8
  import { Tab } from './Tab';
9
+ import { TabGroupContext } from './TabGroupContext';
9
10
 
10
11
  /**
11
12
  * Tabs organize content across different screens and views
@@ -21,22 +22,28 @@ export const Tabs = ({
21
22
  setSelectedTab: externalSetSelectedTab,
22
23
  scrollable = false,
23
24
  }: ReactProps<TabsInterface>) => {
25
+ const tabGroupContext = useContext(TabGroupContext);
26
+
24
27
  const [internalSelectedTab, internalSetSelectedTab] = useState<number | null>(
25
28
  null,
26
29
  );
27
30
 
31
+ // Priorité : props > context > état interne
28
32
  let selectedTab: number | null;
29
- if (externalSelectedTab == 0 || externalSelectedTab != undefined) {
33
+ if (externalSelectedTab === 0 || externalSelectedTab != undefined) {
30
34
  selectedTab = externalSelectedTab;
35
+ } else if (tabGroupContext) {
36
+ selectedTab = tabGroupContext.selectedTab;
31
37
  } else {
32
38
  selectedTab = internalSelectedTab;
33
39
  }
34
40
 
35
- const setSelectedTab = externalSetSelectedTab || internalSetSelectedTab;
41
+ const setSelectedTab =
42
+ externalSetSelectedTab ?? tabGroupContext?.setSelectedTab ?? internalSetSelectedTab;
36
43
 
37
44
  const tabChildren = React.Children.toArray(children).filter(
38
45
  (child) => React.isValidElement(child) && child.type === Tab,
39
- );
46
+ ) as React.ReactElement<TabProps>[];
40
47
 
41
48
  const ref = React.useRef<HTMLDivElement | null>(null);
42
49
 
@@ -60,7 +67,10 @@ export const Tabs = ({
60
67
  }
61
68
  };
62
69
 
63
- const tabsId = useMemo(() => uuidv4(), []);
70
+ const tabsId = useMemo(
71
+ () => tabGroupContext?.tabsId ?? uuidv4(),
72
+ [tabGroupContext?.tabsId],
73
+ );
64
74
 
65
75
  const styles = useTabsStyle({
66
76
  children,
@@ -71,10 +81,11 @@ export const Tabs = ({
71
81
  className,
72
82
  variant,
73
83
  });
84
+
74
85
  return (
75
86
  <div ref={ref} role="tablist" className={styles.tabs}>
76
87
  {tabChildren.map((child, index) => {
77
- return React.cloneElement(child as React.ReactElement<TabProps>, {
88
+ return React.cloneElement(child, {
78
89
  key: index,
79
90
  index,
80
91
  variant: variant,
@@ -17,6 +17,9 @@ export * from './Snackbar';
17
17
  export * from './Switch';
18
18
  export * from './Tab';
19
19
  export * from './Tabs';
20
+ export * from './TabGroup';
21
+ export * from './TabPanels';
22
+ export * from './TabPanel';
20
23
  export * from './TextField';
21
24
  export * from './NavigationRailItem';
22
25
  export * from './NavigationRail';
@@ -1,142 +1,98 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { CustomScrollInterface } from './custom-scroll';
3
- import { ReactProps } from '../utils';
4
- import { BlockScroll } from './block-scroll.effect';
5
- import { animate, AnimationPlaybackControls } from 'motion';
1
+ import { useEffect, useRef, ReactNode } from 'react';
2
+ import Lenis from 'lenis';
3
+
4
+ export type SmoothScrollProps = {
5
+ /**
6
+ * Duration of the scroll animation in seconds or as a CSS string (e.g., '1s', '500ms').
7
+ * Default: 1.2
8
+ */
9
+ transition?: number | string;
10
+ /**
11
+ * Easing function for the scroll animation.
12
+ * Default: easeOutQuint
13
+ */
14
+ easing?: (t: number) => number;
15
+ /**
16
+ * Scroll orientation.
17
+ * Default: 'vertical'
18
+ */
19
+ orientation?: 'vertical' | 'horizontal';
20
+ /**
21
+ * Enable smooth scrolling on touch devices.
22
+ * Default: false (native touch scrolling is usually preferred)
23
+ */
24
+ smoothTouch?: boolean;
25
+ /**
26
+ * Multiplier for touch scroll sensitivity.
27
+ * Default: 2
28
+ */
29
+ touchMultiplier?: number;
30
+ /**
31
+ * Children elements (optional, component works at document level)
32
+ */
33
+ children?: ReactNode;
34
+ };
6
35
 
7
36
  /**
8
- * WARNING: using this component is not recommended for now.
9
- * It may block or alter certain scroll events (wheel/touch/keyboard) depending on the context.
10
- * Rework it later (e.g., via Lenis or another solution) before using it in production.
37
+ * SmoothScroll component using Lenis for smooth scrolling.
38
+ * This component enables smooth scrolling at the document level.
11
39
  */
12
40
  export const SmoothScroll = ({
13
- transition,
41
+ transition = 1.2,
42
+ easing,
14
43
  orientation = 'vertical',
15
- throttleDuration = 25,
16
- }: {
17
- transition?: {
18
- ease:
19
- | 'linear'
20
- | 'easeIn'
21
- | 'easeOut'
22
- | 'easeInOut'
23
- | 'circIn'
24
- | 'circOut'
25
- | 'circInOut'
26
- | 'backIn'
27
- | 'backOut'
28
- | 'backInOut'
29
- | 'anticipate'
30
- | ((t: number) => number);
31
- duration?: number;
32
- };
33
- } & ReactProps<CustomScrollInterface>) => {
34
- // Target value (instant), driven by wheel/touch/keyboard or native scroll sync
35
- const [scrollY, setScrollY] = useState(0);
36
-
37
- const [el, setEl] = useState<HTMLHtmlElement>();
38
-
39
- const isScrolling = useRef(false);
40
- const scrollTimeoutRef = useRef<NodeJS.Timeout>();
41
- const lastAppliedYRef = useRef(0);
42
-
43
- useEffect(() => {
44
- setEl(document as unknown as HTMLHtmlElement);
45
- const y = document.documentElement.scrollTop;
46
- setScrollY(y);
47
- lastAppliedYRef.current = y;
48
- }, []);
44
+ smoothTouch = false,
45
+ touchMultiplier = 2,
46
+ children,
47
+ }: SmoothScrollProps) => {
48
+ const lenisRef = useRef<Lenis | null>(null);
49
+ const rafRef = useRef<number | null>(null);
49
50
 
50
- // Sync native scroll (e.g., scrollbar, programmatic) back to target after a small delay
51
51
  useEffect(() => {
52
- const onScroll = () => {
53
- if (isScrolling.current) return;
54
- setScrollY(document.documentElement.scrollTop);
55
- };
56
-
57
- el?.addEventListener('scroll', onScroll as unknown as EventListener);
58
- return () => {
59
- if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
60
- el?.removeEventListener('scroll', onScroll as unknown as EventListener);
61
- };
62
- }, [el]);
63
-
64
- // Drive the spring when target changes
65
- const currentY = useRef<number | null>();
66
- const animationRef = useRef<AnimationPlaybackControls | null>(null);
67
- useEffect(() => {
68
- const y = scrollY;
69
-
70
- if (animationRef.current) {
71
- animationRef.current.stop();
72
- animationRef.current = null;
52
+ // Parse duration from string if needed (e.g., '1s' -> 1, '500ms' -> 0.5)
53
+ let duration: number;
54
+ if (typeof transition === 'string') {
55
+ if (transition.endsWith('ms')) {
56
+ duration = parseFloat(transition) / 1000;
57
+ } else if (transition.endsWith('s')) {
58
+ duration = parseFloat(transition);
59
+ } else {
60
+ duration = parseFloat(transition) || 1.2;
61
+ }
62
+ } else {
63
+ duration = transition;
73
64
  }
74
65
 
75
- if (!isScrolling.current) {
76
- currentY.current = y;
77
- return;
78
- }
79
- animationRef.current = animate(currentY.current ?? y, y, {
80
- duration: (transition?.duration ?? 500) / 1000,
81
- ease: transition?.ease ?? 'easeOut',
66
+ // Default easing: easeOutQuint
67
+ const defaultEasing = (t: number) => 1 - Math.pow(1 - t, 5);
68
+
69
+ // Initialize Lenis
70
+ lenisRef.current = new Lenis({
71
+ duration,
72
+ easing: easing ?? defaultEasing,
73
+ orientation,
74
+ smoothWheel: true,
75
+ touchMultiplier,
76
+ syncTouch: smoothTouch,
77
+ });
82
78
 
83
- onUpdate: (value) => {
84
- if (scrollTimeoutRef.current) {
85
- clearTimeout(scrollTimeoutRef.current);
86
- }
87
- currentY.current = value;
79
+ // Animation frame loop
80
+ const raf = (time: number) => {
81
+ lenisRef.current?.raf(time);
82
+ rafRef.current = requestAnimationFrame(raf);
83
+ };
88
84
 
89
- const html = document.documentElement;
90
- // Avoid micro-movements causing extra layout work
91
- const rounded = Math.round(value * 1000) / 1000;
92
- const last = lastAppliedYRef.current;
93
- if (Math.abs(rounded - last) < 0.1) return;
94
- lastAppliedYRef.current = rounded;
85
+ rafRef.current = requestAnimationFrame(raf);
95
86
 
96
- if (isScrolling.current) {
97
- html.scrollTo({ top: rounded });
98
- }
99
- },
100
- onComplete: () => {
101
- scrollTimeoutRef.current = setTimeout(() => {
102
- isScrolling.current = false;
103
- }, 300);
104
- animationRef.current = null;
105
- },
106
- });
87
+ // Cleanup
107
88
  return () => {
108
- // Safety: stop if effect re-runs quickly
109
- if (animationRef.current) {
110
- animationRef.current.stop();
111
- animationRef.current = null;
89
+ if (rafRef.current !== null) {
90
+ cancelAnimationFrame(rafRef.current);
112
91
  }
92
+ lenisRef.current?.destroy();
93
+ lenisRef.current = null;
113
94
  };
114
- }, [scrollY]);
115
-
116
- if (!el) return null;
117
-
118
- return (
119
- <BlockScroll
120
- touch={false}
121
- el={el as unknown as HTMLElement}
122
- onScroll={(scroll) => {
123
- if (
124
- 'deltaY' in scroll &&
125
- scroll.deltaY !== 0 &&
126
- el &&
127
- scrollY !== null
128
- ) {
129
- let y = scrollY + scroll.deltaY;
130
- const html = el.querySelector('html');
131
- if (html) {
132
- y = Math.min(y, html.scrollHeight - html.clientHeight);
133
- }
134
- y = Math.max(y, 0);
135
- setScrollY(y);
95
+ }, [transition, easing, orientation, smoothTouch, touchMultiplier]);
136
96
 
137
- isScrolling.current = true;
138
- }
139
- }}
140
- ></BlockScroll>
141
- );
97
+ return children ? <>{children}</> : null;
142
98
  };
@@ -15,6 +15,8 @@ export * from './snackbar.interface';
15
15
  export * from './switch.interface';
16
16
  export * from './tab.interface';
17
17
  export * from './tabs.interface';
18
+ export * from './tab-group.interface';
19
+ export * from './tab-panels.interface';
18
20
  export * from './text-field.interface';
19
21
  export * from './navigation-rail-item.interface';
20
22
  export * from './tooltip.interface';
@@ -0,0 +1,13 @@
1
+ import { Dispatch, ReactNode, SetStateAction } from 'react';
2
+
3
+ export interface TabGroupInterface {
4
+ type: 'div';
5
+ props: {
6
+ children: ReactNode;
7
+ selectedTab?: number | null;
8
+ setSelectedTab?: Dispatch<SetStateAction<number | null>>;
9
+ defaultTab?: number;
10
+ };
11
+ states: object;
12
+ elements: ['tabGroup'];
13
+ }
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface TabPanelsInterface {
4
+ type: 'div';
5
+ props: {
6
+ children: ReactNode;
7
+ };
8
+ states: object;
9
+ elements: ['tabPanels'];
10
+ }
11
+
12
+ export interface TabPanelInterface {
13
+ type: 'div';
14
+ props: {
15
+ children: ReactNode;
16
+ };
17
+ states: {
18
+ isSelected: boolean;
19
+ };
20
+ elements: ['tabPanel'];
21
+ }
@@ -1,6 +1,6 @@
1
1
  import { ActionOrLink } from '../utils/component';
2
2
  import { TabsVariant } from './tabs.interface';
3
- import { Dispatch, RefObject, SetStateAction } from 'react';
3
+ import { Dispatch, ReactNode, RefObject, SetStateAction } from 'react';
4
4
  import { Icon } from '../icon';
5
5
 
6
6
  export type TabProps = {
@@ -18,6 +18,7 @@ export type TabProps = {
18
18
  ) => void;
19
19
  index?: number;
20
20
  scrollable?: boolean;
21
+ children?: ReactNode;
21
22
  };
22
23
 
23
24
  type Elements = ['tab', 'stateLayer', 'icon', 'label', 'content', 'underline'];
@@ -15,6 +15,7 @@ export * from './snackbar.style';
15
15
  export * from './switch.style';
16
16
  export * from './tab.style';
17
17
  export * from './tabs.style';
18
+ export * from './tab-panels.style';
18
19
  export * from './text-field.style';
19
20
  export * from './tooltip.style';
20
21
  export { useButtonStyle } from './button.style';
@@ -0,0 +1,35 @@
1
+ import { TabPanelsInterface, TabPanelInterface } from '../interfaces';
2
+ import {
3
+ type ClassNameComponent,
4
+ classNames,
5
+ createUseClassNames,
6
+ defaultClassNames,
7
+ } from '../utils';
8
+
9
+ const tabPanelsConfig: ClassNameComponent<TabPanelsInterface> = () => ({
10
+ tabPanels: classNames('overflow-hidden'),
11
+ });
12
+
13
+ export const tabPanelsStyle = defaultClassNames<TabPanelsInterface>(
14
+ 'tabPanels',
15
+ tabPanelsConfig,
16
+ );
17
+
18
+ export const useTabPanelsStyle = createUseClassNames<TabPanelsInterface>(
19
+ 'tabPanels',
20
+ tabPanelsConfig,
21
+ );
22
+
23
+ const tabPanelConfig: ClassNameComponent<TabPanelInterface> = () => ({
24
+ tabPanel: classNames(''),
25
+ });
26
+
27
+ export const tabPanelStyle = defaultClassNames<TabPanelInterface>(
28
+ 'tabPanel',
29
+ tabPanelConfig,
30
+ );
31
+
32
+ export const useTabPanelStyle = createUseClassNames<TabPanelInterface>(
33
+ 'tabPanel',
34
+ tabPanelConfig,
35
+ );
@@ -2,42 +2,51 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { SmoothScroll } from '../../';
3
3
  import { JSX } from 'react/jsx-runtime';
4
4
 
5
- // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
6
5
  const meta = {
7
6
  title: 'effect/SmoothScroll',
8
7
  component: SmoothScroll,
9
- parameters: {
10
- // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
11
- },
12
- // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
8
+ parameters: {},
13
9
  tags: ['autodocs'],
14
- // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
15
- argTypes: {},
10
+ argTypes: {
11
+ transition: {
12
+ control: { type: 'text' },
13
+ description: 'Duration of the scroll animation (e.g., "1s", "500ms", or number in seconds)',
14
+ },
15
+ orientation: {
16
+ control: { type: 'select' },
17
+ options: ['vertical', 'horizontal'],
18
+ },
19
+ smoothTouch: {
20
+ control: { type: 'boolean' },
21
+ description: 'Enable smooth scrolling on touch devices',
22
+ },
23
+ touchMultiplier: {
24
+ control: { type: 'number' },
25
+ description: 'Multiplier for touch scroll sensitivity',
26
+ },
27
+ },
16
28
  } satisfies Meta<typeof SmoothScroll>;
17
29
 
18
30
  export default meta;
19
31
  type Story = StoryObj<typeof meta>;
20
32
 
21
- // More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
22
-
23
33
  const createSmoothScrollStory = () => {
24
34
  const SmoothScrollStory: () => JSX.Element = () => (
25
- <div className={'h-96'}>
26
- <SmoothScroll transition={'1s'}>
27
- <div className={' h-52 bg-primary'} />
28
- <div className={' h-52 bg-secondary'} />
29
- <div className={' h-52 bg-tertiary'} />{' '}
30
- <div className={' h-52 bg-primary'} />
31
- <div className={' h-52 bg-secondary'} />
32
- <div className={' h-52 bg-tertiary'} />{' '}
33
- <div className={' h-52 bg-primary'} />
34
- <div className={' h-52 bg-secondary'} />
35
- <div className={' h-52 bg-tertiary'} />{' '}
36
- <div className={' h-52 bg-primary'} />
37
- <div className={' h-52 bg-secondary'} />
38
- <div className={' h-52 bg-tertiary'} />
39
- </SmoothScroll>
40
- </div>
35
+ <>
36
+ <SmoothScroll transition="1s" />
37
+ <div className="h-52 bg-primary" />
38
+ <div className="h-52 bg-secondary" />
39
+ <div className="h-52 bg-tertiary" />
40
+ <div className="h-52 bg-primary" />
41
+ <div className="h-52 bg-secondary" />
42
+ <div className="h-52 bg-tertiary" />
43
+ <div className="h-52 bg-primary" />
44
+ <div className="h-52 bg-secondary" />
45
+ <div className="h-52 bg-tertiary" />
46
+ <div className="h-52 bg-primary" />
47
+ <div className="h-52 bg-secondary" />
48
+ <div className="h-52 bg-tertiary" />
49
+ </>
41
50
  );
42
51
 
43
52
  return SmoothScrollStory;