@growth-angels/ds-core 0.0.2 → 0.0.3

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 (43) hide show
  1. package/package.json +3 -3
  2. package/src/atoms/Button/Button.scss +31 -0
  3. package/src/atoms/Button/Button.stories.tsx +38 -0
  4. package/src/atoms/Button/Button.tsx +25 -0
  5. package/src/atoms/Button/Button.types.ts +16 -0
  6. package/src/atoms/Icon/Icon.scss +19 -0
  7. package/src/atoms/Icon/Icon.stories.tsx +27 -0
  8. package/src/atoms/Icon/Icon.tsx +10 -0
  9. package/src/atoms/Icon/Icon.types.ts +5 -0
  10. package/src/atoms/Text/Text.scss +4 -0
  11. package/src/atoms/Text/Text.tsx +12 -0
  12. package/src/atoms/Text/Text.types.ts +6 -0
  13. package/src/atoms/atoms.scss +3 -0
  14. package/src/atoms/atoms.ts +1 -0
  15. package/src/global.types.ts +3 -0
  16. package/src/hooks/useBreakPointObserver.ts +25 -0
  17. package/src/hooks/useReactAdaptater.ts +8 -0
  18. package/src/hooks/useThemeAssets.ts +15 -0
  19. package/src/index.ts +4 -0
  20. package/src/layout/Container/Container.scss +13 -0
  21. package/src/layout/Container/Container.tsx +10 -0
  22. package/src/layout/Container/Container.types.ts +9 -0
  23. package/src/layout/Grid/Grid.scss +7 -0
  24. package/src/layout/Grid/Grid.tsx +10 -0
  25. package/src/layout/Grid/Grid.types.ts +10 -0
  26. package/src/layout/Grid/GridItem/GridItem.scss +17 -0
  27. package/src/layout/Grid/GridItem/GridItem.tsx +15 -0
  28. package/src/layout/Grid/GridItem/GridItem.types.ts +16 -0
  29. package/src/layout/layout.scss +3 -0
  30. package/src/layout/layout.ts +3 -0
  31. package/src/organisms/Card/Card.scss +60 -0
  32. package/src/organisms/Card/Card.tsx +8 -0
  33. package/src/organisms/Card/Card.types.ts +8 -0
  34. package/src/organisms/Card/CardDefault.stories.tsx +55 -0
  35. package/src/organisms/Card/CardEditorial.stories.tsx +55 -0
  36. package/src/organisms/Carousel/Carousel.scss +85 -0
  37. package/src/organisms/Carousel/Carousel.stories.tsx +35 -0
  38. package/src/organisms/Carousel/Carousel.tsx +184 -0
  39. package/src/organisms/Carousel/Carousel.types.ts +20 -0
  40. package/src/organisms/organisms.scss +2 -0
  41. package/src/organisms/organisms.ts +2 -0
  42. package/src/stories/Configure.mdx +1 -0
  43. package/src/utils.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-angels/ds-core",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Design system by Growth Angels",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -9,7 +9,7 @@
9
9
  "types": "dist/index.d.ts",
10
10
  "style": "src/index.scss",
11
11
  "sideEffects": [
12
- "src/index.scss"
12
+ "src/**/*.scss"
13
13
  ],
14
14
  "exports": {
15
15
  ".": {
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "files": [
22
22
  "dist",
23
- "src/index.scss"
23
+ "src"
24
24
  ],
25
25
  "scripts": {
26
26
  "clean": "rm -rf dist",
@@ -0,0 +1,31 @@
1
+ .ga-ds-btn {
2
+ padding: var(--ga-button-padding);
3
+ display: inline-flex;
4
+ border: none;
5
+ font-size: var(--ga-font-sizes-base);
6
+ font-weight: var(--ga-font-weight-base);
7
+ font-family: var(--ga-font-family-button);
8
+ border-radius: var(--ga-button-radius, 0);
9
+ cursor: pointer;
10
+
11
+ &--primary {
12
+ background-color: var(--ga-button-background-primary, #eee);
13
+ color: var(--ga-button-color-primary, #000);
14
+ }
15
+
16
+ &--secondary {
17
+ background-color: var(--ga-button-background-secondary, #424242);
18
+ color: var(--ga-button-color-secondary, #fff);
19
+ }
20
+
21
+ &--link {
22
+ color: var(--ga-button-color-link, purple);
23
+ padding: 0;
24
+ }
25
+
26
+ &--icon {
27
+ --ga-button-padding: 0.8rem 1.2rem;
28
+ justify-content: center;
29
+ align-items: center;
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Button } from './Button'
3
+
4
+ const meta: Meta<typeof Button> = {
5
+ title: 'Atoms/Button',
6
+ component: Button,
7
+ tags: ['autodocs'],
8
+ }
9
+
10
+ export default meta
11
+ type Story = StoryObj<typeof Button>
12
+
13
+ export const Primary: Story = {
14
+ args: {
15
+ variant: 'primary',
16
+ children: 'Button',
17
+ as: 'a',
18
+ href: '#',
19
+ },
20
+ }
21
+
22
+ export const Secondary: Story = {
23
+ args: {
24
+ variant: 'secondary',
25
+ children: 'Button',
26
+ as: 'a',
27
+ href: '#',
28
+ },
29
+ }
30
+
31
+ export const Link: Story = {
32
+ args: {
33
+ variant: 'link',
34
+ children: 'Button',
35
+ as: 'a',
36
+ href: '#',
37
+ },
38
+ }
@@ -0,0 +1,25 @@
1
+ import { JSX } from 'react/jsx-runtime'
2
+ import type { ButtonProps } from './Button.types'
3
+ import React from 'react'
4
+ import { Icon } from '../Icon/Icon'
5
+
6
+ export const Button = <T extends React.ElementType = "button">(props: ButtonProps<T>): JSX.Element => {
7
+ const { as, children, variant, preventDefault, icon, extraClassNames, ...restProps } = props
8
+ let customVariant = variant
9
+ if(icon) {
10
+ customVariant = 'icon'
11
+ }
12
+ const classNames = ['ga-ds-btn', `ga-ds-btn--${customVariant}`, ...(extraClassNames || [])]
13
+ const handleClick: React.MouseEventHandler<React.ElementRef<T>> = (e) => {
14
+ if (preventDefault) e.preventDefault()
15
+ props?.onClick?.(e)
16
+ }
17
+ return React.createElement(as || 'button', {
18
+ ...restProps,
19
+ className: classNames.join(' '),
20
+ onClick: handleClick
21
+ }, <>
22
+ {icon && <Icon name={icon} size='sm' />}
23
+ {children && children}
24
+ </>)
25
+ }
@@ -0,0 +1,16 @@
1
+ import { WordpressDefault } from "../../global.types.js"
2
+ import React, { ElementType, ComponentPropsWithoutRef } from "react"
3
+
4
+ export type Variant = "primary" | "secondary" | "tertiary" | "link" | "icon"
5
+
6
+ export type ButtonProps<T extends ElementType = "button"> = {
7
+ as?: T
8
+ variant?: Variant
9
+ hasIcon?: "left" | "right"
10
+ icon?: "chevron-left" | "chevron-right"
11
+ preventDefault?: boolean
12
+ children?: React.ReactNode
13
+ } & WordpressDefault &
14
+ Omit<ComponentPropsWithoutRef<T>, "as" | "className" | "onClick"> & {
15
+ onClick?: React.MouseEventHandler<React.ElementRef<T>>
16
+ }
@@ -0,0 +1,19 @@
1
+ .ga-ds-icon {
2
+ width: 2.4rem;
3
+ height: 2.4rem;
4
+
5
+ &--sm {
6
+ width: 1.8rem;
7
+ height: 1.8rem;
8
+ }
9
+
10
+ &--md {
11
+ width: 3rem;
12
+ height: 3rem;
13
+ }
14
+
15
+ &--lg {
16
+ width: 4rem;
17
+ height: 4rem;
18
+ }
19
+ }
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Icon } from './Icon'
3
+
4
+ const meta: Meta<typeof Icon> = {
5
+ title: 'Atoms/Icon',
6
+ component: Icon,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ name: {
10
+ control: 'select',
11
+ options: ['chevron-left', 'chevron-right'],
12
+ },
13
+ size:{
14
+ control: 'radio',
15
+ options: [undefined, 'sm', 'md', 'lg'],
16
+ },
17
+ }
18
+ }
19
+
20
+ export default meta
21
+ type Story = StoryObj<typeof Icon>
22
+
23
+ export const Primary: Story = {
24
+ args: {
25
+ name: 'chevron-left'
26
+ }
27
+ }
@@ -0,0 +1,10 @@
1
+ import { useThemeAssets } from "../../hooks/useThemeAssets";
2
+ import { IconProps } from "./Icon.types";
3
+
4
+ export const Icon = ({ name, size }: IconProps) => {
5
+ const { iconsSprite } = useThemeAssets()
6
+ const classeNames = ['ga-ds-icon', ...(size ? [`ga-ds-icon--${size}`] : [])]
7
+ return <svg className={classeNames.join(' ')} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
8
+ <use xlinkHref={`${iconsSprite}#${name}`} />
9
+ </svg>
10
+ }
@@ -0,0 +1,5 @@
1
+ export interface IconProps {
2
+ name: string
3
+ size?: 'sm' | 'md' | 'lg'
4
+ spriteFile?: string
5
+ }
@@ -0,0 +1,4 @@
1
+ .ga-ds-heading {
2
+ font-weight: var(--ga-font-weight-heading);
3
+ font-family: var(--ga-font-family-heading);
4
+ }
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { TextProps } from "./Text.types";
3
+
4
+ export const Text = (props: TextProps) => {
5
+ const { as, children, classNames, type = "paragraph" } = props
6
+
7
+ const className = typeof classNames === 'string' ? classNames : classNames?.join(' ') || ''
8
+
9
+ return React.createElement(as, {
10
+ className: `ga-ds-${type} ${className}`,
11
+ }, children)
12
+ }
@@ -0,0 +1,6 @@
1
+ export interface TextProps {
2
+ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span'
3
+ classNames?: string[] | string
4
+ type?: 'heading' | 'paragraph'
5
+ children: string
6
+ }
@@ -0,0 +1,3 @@
1
+ @use './Button/Button.scss';
2
+ @use './Text/Text.scss';
3
+ @use './Icon/Icon.scss';
@@ -0,0 +1 @@
1
+ export * from './Button/Button'
@@ -0,0 +1,3 @@
1
+ export interface WordpressDefault {
2
+ extraClassNames?: string[]
3
+ }
@@ -0,0 +1,25 @@
1
+ import { useReactAdapter } from './useReactAdaptater'
2
+
3
+ export function useBreakpointObserver(breakpoints = { sm: 640, md: 1024, lg: 1280 }) {
4
+ const { useEffect, useState } = useReactAdapter()
5
+ const [current, setCurrent] = useState<'sm' | 'md' | 'lg'>('sm')
6
+ useEffect(() => {
7
+ let frameId: number
8
+
9
+ const check = () => {
10
+ const width = window.innerWidth
11
+ let bp: 'sm' | 'md' | 'lg' = 'sm'
12
+
13
+ if (width >= breakpoints.md) bp = 'lg'
14
+ else if (width >= breakpoints.sm) bp = 'md'
15
+
16
+ setCurrent((prev) => (prev !== bp ? bp : prev))
17
+ frameId = requestAnimationFrame(check)
18
+ }
19
+
20
+ frameId = requestAnimationFrame(check)
21
+ return () => cancelAnimationFrame(frameId)
22
+ }, [breakpoints])
23
+
24
+ return current
25
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react'
2
+
3
+ export const useReactAdapter = () => {
4
+ if (typeof window === 'undefined' || !(window as typeof window & { wp: { element: typeof React } }).wp || !(window as typeof window & { wp: { element: typeof React } }).wp.element) {
5
+ return React
6
+ }
7
+ return (window as typeof window & { wp: { element: typeof React } }).wp.element
8
+ }
@@ -0,0 +1,15 @@
1
+ let defaultThemeAssets = {
2
+ iconsSprite: '/assets/svg/sprites/icons.svg'
3
+ }
4
+
5
+ export const setThemeAssets = (assets: typeof defaultThemeAssets) => {
6
+ defaultThemeAssets = {
7
+ ...defaultThemeAssets,
8
+ ...assets,
9
+ }
10
+ }
11
+
12
+
13
+ export const useThemeAssets = () => {
14
+ return defaultThemeAssets
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './atoms/atoms'
2
+ export * from './organisms/organisms'
3
+ export * from './layout/layout'
4
+ export { setThemeAssets } from './hooks/useThemeAssets'
@@ -0,0 +1,13 @@
1
+ @use 'sass:map';
2
+ $sizes: (
3
+ "large": "var(--ga-container-large)",
4
+ "medium": "var(--ga-container-medium)",
5
+ "small": "var(--ga-container-small)",
6
+ );
7
+
8
+ @each $key, $value in $sizes {
9
+ .ga-ds-container--#{$key} {
10
+ padding-left: calc((100% - #{$value}) / 2);
11
+ padding-right: calc((100% - #{$value}) / 2);
12
+ }
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { JSX } from 'react/jsx-runtime'
2
+ import type { ContainerProps } from './Container.types'
3
+
4
+ export const Container = (props: ContainerProps): JSX.Element => {
5
+ const { size, children } = props
6
+ const classes = ['ga-ds-container', `ga-ds-container--${size}`, ...(props.extraClassNames || [])]
7
+ return <div className={classes.join(' ')}>
8
+ {children}
9
+ </div>
10
+ }
@@ -0,0 +1,9 @@
1
+ import { WordpressDefault } from "../../global.types";
2
+
3
+ export type ContainerSize = 'large' | 'medium' | 'small';
4
+
5
+
6
+ export interface ContainerProps extends WordpressDefault {
7
+ size: ContainerSize;
8
+ children: React.ReactNode | React.ReactNode[];
9
+ }
@@ -0,0 +1,7 @@
1
+ @use './GridItem/GridItem.scss';
2
+
3
+ .ga-ds-grid {
4
+ display: grid;
5
+ gap: var(--ga-grid-gutter);
6
+ grid-template-columns: repeat(var(--ga-grid-columns), 1fr);
7
+ }
@@ -0,0 +1,10 @@
1
+ import type { JSX } from 'react/jsx-runtime'
2
+ import { GridProps } from './Grid.types'
3
+ export const Grid = (props: GridProps): JSX.Element => {
4
+ const { children, alignItems, justifyContent, extraClassNames } = props
5
+ const classNames = ['ga-ds-grid', ...(extraClassNames || [])]
6
+
7
+ return <div className={classNames.join(' ')} style={{ alignItems, justifyContent }}>
8
+ {children}
9
+ </div>
10
+ }
@@ -0,0 +1,10 @@
1
+ import { WordpressDefault } from "../../global.types";
2
+
3
+ export interface Attributes {
4
+ alignItems: 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline'
5
+ justifyContent: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around'
6
+ }
7
+
8
+ export interface GridProps extends WordpressDefault, Attributes {
9
+ children: React.ReactNode | React.ReactNode[];
10
+ }
@@ -0,0 +1,17 @@
1
+ @use "@ga/foundation/config" as *;
2
+
3
+ .ga-ds-grid-item {
4
+ grid-column: span var(--ga-ds-grid-item-size-xs);
5
+
6
+ @include bp-up(sm) {
7
+ grid-column: span var(--ga-ds-grid-item-size-sm);
8
+ }
9
+
10
+ @include bp-up(md) {
11
+ grid-column: span var(--ga-ds-grid-item-size-md);
12
+ }
13
+
14
+ @include bp-up(lg) {
15
+ grid-column: span var(--ga-ds-grid-item-size-lg);
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ import type { JSX } from 'react/jsx-runtime'
2
+ import { GridItemProps } from './GridItem.types'
3
+ export const GridItem = (props: GridItemProps): JSX.Element => {
4
+ const { children, sizes = {
5
+ xs: 12,
6
+ sm: 12,
7
+ md: 12,
8
+ lg: 12,
9
+ } } = props
10
+
11
+ const style = Object.fromEntries(Object.entries(sizes).map(([key, value]) => [`--ga-ds-grid-item-size-${key}`, `${value}`]))
12
+ return <div className="ga-ds-grid-item" style={style}>
13
+ {children}
14
+ </div>
15
+ }
@@ -0,0 +1,16 @@
1
+ import { WordpressDefault } from "../../../global.types";
2
+
3
+ export type ColumnSize = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'
4
+
5
+ export interface Attributes {
6
+ sizes: {
7
+ xs: ColumnSize;
8
+ sm: ColumnSize;
9
+ md: ColumnSize;
10
+ lg: ColumnSize;
11
+ }
12
+ }
13
+
14
+ export interface GridItemProps extends WordpressDefault, Attributes {
15
+ children: React.ReactNode | React.ReactNode[];
16
+ }
@@ -0,0 +1,3 @@
1
+ @use './Container/Container.scss';
2
+ @use './Grid/Grid.scss';
3
+ @use './Grid/GridItem/GridItem.scss';
@@ -0,0 +1,3 @@
1
+ export * from './Container/Container'
2
+ export * from './Grid/Grid'
3
+ export * from './Grid/GridItem/GridItem'
@@ -0,0 +1,60 @@
1
+ .ga-ds-card {
2
+ --card-spacing: var(--ga-card-spacing);
3
+ width: 100%;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--ga-card-gap);
7
+ border-radius: var(--ga-card-radius, 0);
8
+ overflow: hidden;
9
+
10
+ &__header {
11
+ overflow: hidden;
12
+ }
13
+
14
+ &__title,
15
+ &__content,
16
+ &__footer {
17
+ padding-left: var(--card-spacing);
18
+ padding-right: var(--card-spacing);
19
+ }
20
+
21
+ &__footer {
22
+ padding-bottom: var(--card-spacing);
23
+ }
24
+
25
+ &--primary {
26
+ background-color: var(--ga-card-background-primary);
27
+ color: var(--ga-card-background-primary-reverted);
28
+ }
29
+
30
+ &--secondary {
31
+ background-color: var(--ga-card-background-secondary);
32
+ color: var(--ga-card-background-secondary-reverted);
33
+ }
34
+ }
35
+
36
+ .ga-ds-card--default {
37
+ .ga-ds-card__header {
38
+ height: var(--card-header-height, 24rem);
39
+
40
+ figure {
41
+ width: 100%;
42
+ height: 100%;
43
+ }
44
+
45
+ img {
46
+ display: block;
47
+ width: 100%;
48
+ height: 100%;
49
+ object-fit: cover;
50
+ object-position: center;
51
+ }
52
+ }
53
+ }
54
+
55
+ .ga-ds-card--editorial {
56
+ .ga-ds-card__header {
57
+ padding-top: var(--card-spacing);
58
+ padding-left: var(--card-spacing);
59
+ }
60
+ }
@@ -0,0 +1,8 @@
1
+ import { JSX } from 'react/jsx-runtime'
2
+ import type { CardProps } from './Card.types'
3
+
4
+ export const Card = ({ children, variant = 'primary', extraClassNames, type = 'default' }: CardProps): JSX.Element => {
5
+ const classNames = ['ga-ds-card', `ga-ds-card--${variant}`, `ga-ds-card--${type}`, ...(extraClassNames || [])]
6
+
7
+ return <div className={classNames.join(' ')}>{children}</div>
8
+ }
@@ -0,0 +1,8 @@
1
+ export type Variant = 'primary' | 'secondary'
2
+ export type CardType = 'default' | 'editorial' | 'image' | 'icon'
3
+ export interface CardProps {
4
+ children: React.ReactNode | React.ReactNode[] | JSX.Element
5
+ variant: Variant
6
+ type?: CardType
7
+ extraClassNames?: string[]
8
+ }
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Card } from './Card'
3
+ import { Button } from '../../atoms/atoms';
4
+ import CardDefaultHeader600x400 from '../../../assets/600x400.svg'
5
+ import CardDefaultHeader50x50 from '../../../assets/50x50.svg'
6
+
7
+ const meta: Meta<typeof Card> = {
8
+ title: 'Organisms/Card/Default',
9
+ component: Card,
10
+ tags: ['autodocs'],
11
+ }
12
+
13
+ export default meta
14
+ type Story = StoryObj<typeof Card>
15
+
16
+ const CardChildren = ({ image } : {
17
+ image: string
18
+ }) => {
19
+ const images = {
20
+ '400x600': CardDefaultHeader600x400,
21
+ '50x50': CardDefaultHeader50x50
22
+ }
23
+ return <>
24
+ <div className="ga-ds-card__header">
25
+ <img src={images[image as keyof typeof images]} alt="Card header" />
26
+ </div>
27
+ <div className="ga-ds-card__title">
28
+ <h2>Card title</h2>
29
+ </div>
30
+ <div className='ga-ds-card__content'>
31
+ <p>
32
+ Lorem ipsum dolor sit amet consectetur, adipisicing elit. Porro temporibus quam ex consequatur est laboriosam nam quisquam doloremque perferendis quo ut, tempora error neque modi delectus assumenda! A, doloremque nulla.
33
+ </p>
34
+ </div>
35
+ <div className="ga-ds-card__footer">
36
+ <Button as="a" variant='primary'>Read more</Button>
37
+ </div>
38
+ </>
39
+ }
40
+
41
+ export const Primary: Story = {
42
+ args: {
43
+ variant: 'primary',
44
+ type: 'default',
45
+ children: <CardChildren image={'400x600'} />
46
+ }
47
+ }
48
+
49
+ export const Secondary: Story = {
50
+ args: {
51
+ variant: 'secondary',
52
+ type: 'default',
53
+ children: <CardChildren image={'400x600'} />
54
+ }
55
+ }
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Card } from './Card'
3
+ import { Button } from '../../atoms/atoms';
4
+ import CardDefaultHeader600x400 from '../../../assets/600x400.svg'
5
+ import CardDefaultHeader50x50 from '../../../assets/50x50.svg'
6
+
7
+ const meta: Meta<typeof Card> = {
8
+ title: 'Organisms/Card/Editorial',
9
+ component: Card,
10
+ tags: ['autodocs'],
11
+ }
12
+
13
+ export default meta
14
+ type Story = StoryObj<typeof Card>
15
+
16
+ const CardChildren = ({ image } : {
17
+ image: string
18
+ }) => {
19
+ const images = {
20
+ '400x600': CardDefaultHeader600x400,
21
+ '50x50': CardDefaultHeader50x50
22
+ }
23
+ return <>
24
+ <div className="ga-ds-card__header">
25
+ <img src={images[image as keyof typeof images]} alt="Card header" />
26
+ </div>
27
+ <div className="ga-ds-card__title">
28
+ <h2>Card title</h2>
29
+ </div>
30
+ <div className='ga-ds-card__content'>
31
+ <p>
32
+ Lorem ipsum dolor sit amet consectetur, adipisicing elit. Porro temporibus quam ex consequatur est laboriosam nam quisquam doloremque perferendis quo ut, tempora error neque modi delectus assumenda! A, doloremque nulla.
33
+ </p>
34
+ </div>
35
+ <div className="ga-ds-card__footer">
36
+ <Button as="a" variant='primary'>Read more</Button>
37
+ </div>
38
+ </>
39
+ }
40
+
41
+ export const Primary: Story = {
42
+ args: {
43
+ variant: 'primary',
44
+ type: 'editorial',
45
+ children: <CardChildren image={'50x50'} />
46
+ }
47
+ }
48
+
49
+ export const Secondary: Story = {
50
+ args: {
51
+ variant: 'secondary',
52
+ type: 'editorial',
53
+ children: <CardChildren image={'50x50'} />
54
+ }
55
+ }
@@ -0,0 +1,85 @@
1
+ @use "@ga/foundation/config" as *;
2
+
3
+ .ga-ds-carousel {
4
+ overflow: hidden;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: stretch;
8
+ gap: 2rem;
9
+
10
+ &__track {
11
+ --ga-ds-slides-per-view: 1;
12
+ display: grid;
13
+ grid-auto-flow: column; // place les slides horizontalement
14
+ grid-auto-columns: calc(
15
+ (100% - (var(--ga-ds-space-between, 1rem) * (var(--ga-ds-slides-per-view, 3) - 1))) / var(--ga-ds-slides-per-view, 3)
16
+ );
17
+ gap: var(--ga-ds-space-between, 1rem);
18
+ scroll-snap-align: start;
19
+ overflow-x: auto;
20
+ scroll-snap-type: x mandatory;
21
+ -webkit-overflow-scrolling: touch;
22
+ scrollbar-width: none;
23
+ box-sizing: border-box;
24
+ width: 100%;
25
+
26
+ &::-webkit-scrollbar {
27
+ display: none;
28
+ }
29
+
30
+ @include bp-up(xs) {
31
+ --ga-ds-slides-per-view: var(--ga-ds-slides-per-view-sm);
32
+ }
33
+ @include bp-up(sm) {
34
+ --ga-ds-slides-per-view: var(--ga-ds-slides-per-view-md);
35
+ }
36
+ @include bp-up(md) {
37
+ --ga-ds-slides-per-view: var(--ga-ds-slides-per-view-lg);
38
+ }
39
+ }
40
+
41
+ &__slide {
42
+ flex: 0 0 auto;
43
+ width: 100%;
44
+ scroll-snap-align: start;
45
+ }
46
+
47
+ &__arrows {
48
+ display: flex;
49
+
50
+ &.left {
51
+ justify-content: flex-start;
52
+ }
53
+
54
+ &.right {
55
+ justify-content: flex-end;
56
+ }
57
+
58
+ &.center {
59
+ justify-content: center;
60
+ }
61
+ }
62
+
63
+ &__navigation:has(.ga-ds-carousel__arrows):has(.ga-ds-carousel__dots) {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ align-items: center;
67
+ }
68
+
69
+ &__dots {
70
+ display: flex;
71
+ gap: var(--ga-ds-gap, 1rem);
72
+
73
+ .ga-ds-carousel__dot {
74
+ display: block;
75
+ width: 1rem;
76
+ height: 1rem;
77
+ border-radius: 50%;
78
+ background-color: #e2e2e2;
79
+
80
+ &--active {
81
+ background-color: #424242;
82
+ }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,35 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite"
2
+ import { Carousel } from "./Carousel"
3
+
4
+ const meta: Meta<typeof Carousel> = {
5
+ title: "Organisms/Carousel",
6
+ component: Carousel,
7
+ tags: ["autodocs"],
8
+ }
9
+
10
+ export default meta
11
+ type Story = StoryObj<typeof Carousel>
12
+
13
+ export const Primary: Story = {
14
+ args: {
15
+ children: [
16
+ <div>Slide 1</div>,
17
+ <div>Slide 2</div>,
18
+ <div>Slide 3</div>,
19
+ <div>Slide 4</div>,
20
+ <div>Slide 5</div>,
21
+ <div>Slide 6</div>,
22
+ <div>Slide 7</div>,
23
+ ],
24
+ slidesPerView: { sm: 1, md: 2, lg: 3 },
25
+ spaceBetween: 16,
26
+ pagination: {
27
+ clickable: true,
28
+ },
29
+ navigation: {
30
+ positionY: "bottom",
31
+ },
32
+ hasNavigation: true,
33
+ hasPagination: true,
34
+ },
35
+ }
@@ -0,0 +1,184 @@
1
+ import { Button } from "../../atoms/atoms"
2
+ import { useBreakpointObserver } from "../../hooks/useBreakPointObserver"
3
+ import { useReactAdapter } from "../../hooks/useReactAdaptater"
4
+ import { CarouselProps } from "./Carousel.types"
5
+
6
+ export const Carousel = (props: CarouselProps) => {
7
+ const {
8
+ children,
9
+ slidesPerView = { sm: 1, md: 2, lg: 3 },
10
+ spaceBetween = 20,
11
+ navigation,
12
+ pagination,
13
+ context,
14
+ hasPagination,
15
+ hasNavigation,
16
+ } = props
17
+ const { useEffect, useState, useRef, Children } = useReactAdapter()
18
+ const trackRef = useRef<HTMLDivElement>(null)
19
+ const slides = Children.toArray(children)
20
+
21
+ const calculatePages = (totalSlides = 0, slidesPerView = 0, step = 1) => {
22
+ return Math.max(1, Math.ceil((totalSlides - slidesPerView) / step) + 1)
23
+ }
24
+
25
+ const [activeSlideIndex, setActiveSlideIndex] = useState(0)
26
+ const [isAtEnd, setIsAtEnd] = useState(false)
27
+ const [isAtStart, setIsAtStart] = useState(true)
28
+ const [totalPages, setTotalPages] = useState(0)
29
+
30
+ useEffect(() => {
31
+ setIsAtStart(activeSlideIndex === 0)
32
+ setIsAtEnd(activeSlideIndex === totalPages - 1)
33
+ }, [activeSlideIndex, totalPages])
34
+
35
+ const breakpoint = useBreakpointObserver({ sm: 768, md: 992, lg: 1200 })
36
+
37
+ useEffect(() => {
38
+ setTotalPages(calculatePages(slides.length, slidesPerView[breakpoint], 1))
39
+ }, [breakpoint, slidesPerView])
40
+
41
+ const style = Object.fromEntries(
42
+ Object.entries(slidesPerView).map(([key, value]) => [`--ga-ds-slides-per-view-${key}`, `${value}`])
43
+ )
44
+
45
+ const goPrev = () => {
46
+ if (!trackRef.current) return
47
+ const slides = trackRef.current.querySelectorAll(".ga-ds-carousel__slide")
48
+ if (activeSlideIndex === 0) return
49
+
50
+ const nextIndex = activeSlideIndex - 1
51
+ slides[nextIndex]?.scrollIntoView({
52
+ behavior: "smooth",
53
+ block: "nearest",
54
+ inline: "start",
55
+ })
56
+ setActiveSlideIndex(nextIndex)
57
+ }
58
+
59
+ const goNext = () => {
60
+ if (!trackRef.current) return
61
+ const slides = trackRef.current.querySelectorAll(".ga-ds-carousel__slide")
62
+ const totalSlides = slides.length
63
+
64
+ if (activeSlideIndex >= totalSlides - 1) return
65
+ if (isAtEnd) return
66
+
67
+ const nextIndex = activeSlideIndex + 1
68
+ slides[nextIndex]?.scrollIntoView({
69
+ behavior: "smooth",
70
+ block: "nearest",
71
+ inline: "start",
72
+ })
73
+ setActiveSlideIndex(nextIndex)
74
+ }
75
+
76
+ const goTo = (nextIndex: number) => {
77
+ if (!trackRef.current) return
78
+ const slides = trackRef.current.querySelectorAll(".ga-ds-carousel__slide")
79
+ slides[nextIndex]?.scrollIntoView({
80
+ behavior: "smooth",
81
+ block: "nearest",
82
+ inline: "start",
83
+ })
84
+ setActiveSlideIndex(nextIndex)
85
+ }
86
+
87
+ return (
88
+ <div className="ga-ds-carousel" style={style}>
89
+ {navigation?.positionY === "top" && hasNavigation && (
90
+ <CarouselNavigation goPrev={goPrev} goNext={goNext} isAtStart={isAtStart} isAtEnd={isAtEnd} />
91
+ )}
92
+ <div
93
+ className="ga-ds-carousel__track"
94
+ ref={trackRef}
95
+ style={
96
+ {
97
+ "--ga-ds-space-between": `${spaceBetween / 10}rem`,
98
+ } as React.CSSProperties
99
+ }
100
+ >
101
+ {context === "wp-editor"
102
+ ? children
103
+ : slides.map((child, index) => (
104
+ <div
105
+ key={index}
106
+ className={`ga-ds-carousel__slide ${index === activeSlideIndex ? "ga-ds-carousel__slide--active" : ""}`}
107
+ >
108
+ {child}
109
+ </div>
110
+ ))}
111
+ </div>
112
+
113
+ <div className="ga-ds-carousel__navigation">
114
+ {pagination && hasPagination && (
115
+ <CarouselPagination
116
+ totalPages={totalPages}
117
+ activeSlideIndex={activeSlideIndex}
118
+ goTo={goTo}
119
+ clickable={pagination.clickable}
120
+ />
121
+ )}
122
+ {hasNavigation && navigation?.positionY === "bottom" && (
123
+ <CarouselNavigation goPrev={goPrev} goNext={goNext} isAtStart={isAtStart} isAtEnd={isAtEnd} />
124
+ )}
125
+ </div>
126
+ </div>
127
+ )
128
+ }
129
+
130
+ const CarouselNavigation = ({
131
+ goPrev,
132
+ goNext,
133
+ isAtStart,
134
+ isAtEnd,
135
+ }: {
136
+ goPrev: () => void
137
+ goNext: () => void
138
+ isAtStart: boolean
139
+ isAtEnd: boolean
140
+ }) => {
141
+ return (
142
+ <div className={`ga-ds-carousel__arrows`}>
143
+ <Button
144
+ extraClassNames={["ga-ds-carousel__button", "ga-ds-carousel__button--prev"]}
145
+ icon="chevron-left"
146
+ onClick={goPrev}
147
+ disabled={isAtStart}
148
+ />
149
+ <Button
150
+ extraClassNames={["ga-ds-carousel__button", "ga-ds-carousel__button--next"]}
151
+ icon="chevron-right"
152
+ onClick={goNext}
153
+ disabled={isAtEnd}
154
+ />
155
+ </div>
156
+ )
157
+ }
158
+
159
+ const CarouselPagination = ({
160
+ totalPages,
161
+ activeSlideIndex,
162
+ goTo,
163
+ clickable,
164
+ }: {
165
+ totalPages: number
166
+ activeSlideIndex: number
167
+ goTo: (nextIndex: number) => void
168
+ clickable: boolean
169
+ }) => {
170
+ return (
171
+ <div className="ga-ds-carousel__dots">
172
+ {Array.from({ length: totalPages }, (_, index) => index).map((_, index) => (
173
+ <span
174
+ key={index}
175
+ className={`ga-ds-carousel__dot ${index === activeSlideIndex ? "ga-ds-carousel__dot--active" : ""}`}
176
+ onClick={() => clickable && goTo(index)}
177
+ style={{
178
+ cursor: clickable ? "pointer" : "default",
179
+ }}
180
+ ></span>
181
+ ))}
182
+ </div>
183
+ )
184
+ }
@@ -0,0 +1,20 @@
1
+ export type Attributes = {
2
+ context?: 'wp-editor'
3
+ slidesPerView?: {
4
+ sm: number
5
+ md: number
6
+ lg: number
7
+ }
8
+ spaceBetween?: number
9
+ hasPagination?: boolean
10
+ hasNavigation?: boolean
11
+ pagination?: {
12
+ clickable: boolean
13
+ },
14
+ navigation?: {
15
+ positionY: 'bottom' | 'top';
16
+ }
17
+ }
18
+ export interface CarouselProps extends Attributes {
19
+ children: React.ReactNode | React.ReactNode[]
20
+ }
@@ -0,0 +1,2 @@
1
+ @use './Card/Card.scss';
2
+ @use './Carousel/Carousel.scss';
@@ -0,0 +1,2 @@
1
+ export * from './Card/Card'
2
+ export * from './Carousel/Carousel'
@@ -0,0 +1 @@
1
+ # Hello world
package/src/utils.ts ADDED
File without changes