@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.
- package/package.json +3 -3
- package/src/atoms/Button/Button.scss +31 -0
- package/src/atoms/Button/Button.stories.tsx +38 -0
- package/src/atoms/Button/Button.tsx +25 -0
- package/src/atoms/Button/Button.types.ts +16 -0
- package/src/atoms/Icon/Icon.scss +19 -0
- package/src/atoms/Icon/Icon.stories.tsx +27 -0
- package/src/atoms/Icon/Icon.tsx +10 -0
- package/src/atoms/Icon/Icon.types.ts +5 -0
- package/src/atoms/Text/Text.scss +4 -0
- package/src/atoms/Text/Text.tsx +12 -0
- package/src/atoms/Text/Text.types.ts +6 -0
- package/src/atoms/atoms.scss +3 -0
- package/src/atoms/atoms.ts +1 -0
- package/src/global.types.ts +3 -0
- package/src/hooks/useBreakPointObserver.ts +25 -0
- package/src/hooks/useReactAdaptater.ts +8 -0
- package/src/hooks/useThemeAssets.ts +15 -0
- package/src/index.ts +4 -0
- package/src/layout/Container/Container.scss +13 -0
- package/src/layout/Container/Container.tsx +10 -0
- package/src/layout/Container/Container.types.ts +9 -0
- package/src/layout/Grid/Grid.scss +7 -0
- package/src/layout/Grid/Grid.tsx +10 -0
- package/src/layout/Grid/Grid.types.ts +10 -0
- package/src/layout/Grid/GridItem/GridItem.scss +17 -0
- package/src/layout/Grid/GridItem/GridItem.tsx +15 -0
- package/src/layout/Grid/GridItem/GridItem.types.ts +16 -0
- package/src/layout/layout.scss +3 -0
- package/src/layout/layout.ts +3 -0
- package/src/organisms/Card/Card.scss +60 -0
- package/src/organisms/Card/Card.tsx +8 -0
- package/src/organisms/Card/Card.types.ts +8 -0
- package/src/organisms/Card/CardDefault.stories.tsx +55 -0
- package/src/organisms/Card/CardEditorial.stories.tsx +55 -0
- package/src/organisms/Carousel/Carousel.scss +85 -0
- package/src/organisms/Carousel/Carousel.stories.tsx +35 -0
- package/src/organisms/Carousel/Carousel.tsx +184 -0
- package/src/organisms/Carousel/Carousel.types.ts +20 -0
- package/src/organisms/organisms.scss +2 -0
- package/src/organisms/organisms.ts +2 -0
- package/src/stories/Configure.mdx +1 -0
- 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.
|
|
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
|
|
12
|
+
"src/**/*.scss"
|
|
13
13
|
],
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
|
-
"src
|
|
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,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,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 @@
|
|
|
1
|
+
export * from './Button/Button'
|
|
@@ -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,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,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,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 @@
|
|
|
1
|
+
# Hello world
|
package/src/utils.ts
ADDED
|
File without changes
|