diff --git a/packages/components/src/components/FlexBox/FlexBox.module.css b/packages/components/src/components/FlexBox/FlexBox.module.css new file mode 100644 index 000000000..b99f00063 --- /dev/null +++ b/packages/components/src/components/FlexBox/FlexBox.module.css @@ -0,0 +1,199 @@ +.base { + --flex-type: flex; + --flex-wrap: nowrap; + --flex-direction: row; + --flex-align-items: flex-start; + --flex-justify-content: flex-start; + + gap: var(--flex-row-gap) var(--flex-column-gap); + display: var(--flex-type); + align-items: var(--flex-align-items); + justify-content: var(--flex-justify-content); + flex-flow: var(--flex-direction) var(--flex-wrap); +} + +.alignItems_flex-start { + --flex-align-items: flex-start; +} + +.alignItems_flex-end { + --flex-align-items: flex-end; +} + +.alignItems_center { + --flex-align-items: center; +} + +.alignItems_baseline { + --flex-align-items: baseline; +} + +.alignItems_stretch { + --flex-align-items: stretch; +} + +.justifyContent_flex-start { + --flex-justify-content: flex-start; +} + +.justifyContent_flex-end { + --flex-justify-content: flex-end; +} + +.justifyContent_center { + --flex-justify-content: center; +} + +.justifyContent_space-between { + --flex-justify-content: space-between; +} + +.justifyContent_space-around { + --flex-justify-content: space-around; +} + +.justifyContent_space-evenly { + --flex-justify-content: space-evenly; +} + +.flex_flex { + --flex-type: flex; +} + +.flex_inline-flex { + --flex-type: inline-flex; +} + +.wrap_wrap { + --flex-wrap: wrap; +} + +.wrap_nowrap { + --flex-wrap: nowrap; +} + +.wrap_wrap-reverse { + --flex-wrap: wrap-reverse; +} + +.direction_row { + --flex-direction: row; +} + +.direction_row-reverse { + --flex-direction: row-reverse; +} + +.direction_column { + --flex-direction: column; +} + +.direction_column-reverse { + --flex-direction: column-reverse; +} + +/* column-gap */ +.gap_column_3xs { + --flex-column-gap: var(--kbq-size-3xs); +} + +.gap_column_xxs { + --flex-column-gap: var(--kbq-size-xxs); +} + +.gap_column_xs { + --flex-column-gap: var(--kbq-size-xs); +} + +.gap_column_s { + --flex-column-gap: var(--kbq-size-s); +} + +.gap_column_m { + --flex-column-gap: var(--kbq-size-m); +} + +.gap_column_l { + --flex-column-gap: var(--kbq-size-l); +} + +.gap_column_xl { + --flex-column-gap: var(--kbq-size-xl); +} + +.gap_column_xxl { + --flex-column-gap: var(--kbq-size-xxl); +} + +.gap_column_3xl { + --flex-column-gap: var(--kbq-size-3xl); +} + +.gap_column_4xl { + --flex-column-gap: var(--kbq-size-4xl); +} + +.gap_column_5xl { + --flex-column-gap: var(--kbq-size-5xl); +} + +.gap_column_6xl { + --flex-column-gap: var(--kbq-size-6xl); +} + +.gap_column_7xl { + --flex-column-gap: var(--kbq-size-7xl); +} + +/* row-gap */ +.gap_row_3xs { + --flex-row-gap: var(--kbq-size-3xs); +} + +.gap_row_xxs { + --flex-row-gap: var(--kbq-size-xxs); +} + +.gap_row_xs { + --flex-row-gap: var(--kbq-size-xs); +} + +.gap_row_s { + --flex-row-gap: var(--kbq-size-s); +} + +.gap_row_m { + --flex-row-gap: var(--kbq-size-m); +} + +.gap_row_l { + --flex-row-gap: var(--kbq-size-l); +} + +.gap_row_xl { + --flex-row-gap: var(--kbq-size-xl); +} + +.gap_row_xxl { + --flex-row-gap: var(--kbq-size-xxl); +} + +.gap_row_3xl { + --flex-row-gap: var(--kbq-size-3xl); +} + +.gap_row_4xl { + --flex-row-gap: var(--kbq-size-4xl); +} + +.gap_row_5xl { + --flex-row-gap: var(--kbq-size-5xl); +} + +.gap_row_6xl { + --flex-row-gap: var(--kbq-size-6xl); +} + +.gap_row_7xl { + --flex-row-gap: var(--kbq-size-7xl); +} diff --git a/packages/components/src/components/FlexBox/FlexBox.test.tsx b/packages/components/src/components/FlexBox/FlexBox.test.tsx index f4332914f..cb801a9d8 100644 --- a/packages/components/src/components/FlexBox/FlexBox.test.tsx +++ b/packages/components/src/components/FlexBox/FlexBox.test.tsx @@ -1,8 +1,11 @@ import { createRef } from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; +import { BreakpointsContext, type BreakpointsContextType } from '../Provider'; + +import s from './FlexBox.module.css'; import { FlexBox } from './index'; import type { FlexBoxProps } from './index'; @@ -27,4 +30,54 @@ describe('FlexBox', () => { const flexBox = container.querySelector('div'); expect(ref.current).toBe(flexBox); }); + + it('should apply flex classes from props', () => { + render( + + ); + + expect(screen.getByTestId('flex-box')).toHaveClass( + s.base, + s['flex_inline-flex'], + s['wrap_wrap-reverse'], + s.gap_row_l, + s.gap_column_xl, + s['direction_column-reverse'], + s.alignItems_center, + s['justifyContent_space-between'] + ); + }); + + it('should apply flex classes from responsive props', () => { + const breakpoints = { + xs: true, + l: true, + } as BreakpointsContextType; + + render( + + + + ); + + expect(screen.getByTestId('flex-box')).toHaveClass( + s.gap_row_m, + s.gap_column_m, + s.direction_row + ); + }); }); diff --git a/packages/components/src/components/FlexBox/FlexBox.tsx b/packages/components/src/components/FlexBox/FlexBox.tsx index 6e21a3e01..2aa7e8961 100644 --- a/packages/components/src/components/FlexBox/FlexBox.tsx +++ b/packages/components/src/components/FlexBox/FlexBox.tsx @@ -5,9 +5,9 @@ import type { ComponentPropsWithRef, ElementType } from 'react'; import { clsx, polymorphicForwardRef } from '@koobiq/react-core'; import { getResponsiveValue } from '../../utils'; -import { flex as flexBox } from '../layout'; import { useMatchedBreakpoints } from '../Provider'; +import s from './FlexBox.module.css'; import type { FlexBoxBaseProps } from './index'; /** @@ -38,26 +38,29 @@ export const FlexBox = polymorphicForwardRef<'div', FlexBoxBaseProps>( const flex = getResponsiveValue(flexProp, breakpoints); const gap = getResponsiveValue(gapProp, breakpoints); - const colGap = getResponsiveValue(colGapProp, breakpoints); - const rowGap = getResponsiveValue(rowGapProp, breakpoints); + const colGap = getResponsiveValue(colGapProp, breakpoints) ?? gap; + const rowGap = getResponsiveValue(rowGapProp, breakpoints) ?? gap; const wrap = getResponsiveValue(wrapProp, breakpoints); const alignItems = getResponsiveValue(alignItemsProp, breakpoints); const direction = getResponsiveValue(directionProp, breakpoints); const justifyContent = getResponsiveValue(justifyContentProp, breakpoints); - const flexCn = flexBox({ - gap, - flex, - wrap, - colGap, - rowGap, - direction, - alignItems, - justifyContent, - }); - return ( - + {children} ); diff --git a/packages/components/src/components/layout/flex/flex.stories.tsx b/packages/components/src/components/layout/flex/flex.stories.tsx index 3b50be176..07d1a9df0 100644 --- a/packages/components/src/components/layout/flex/flex.stories.tsx +++ b/packages/components/src/components/layout/flex/flex.stories.tsx @@ -10,9 +10,9 @@ import { flexPropGap, flexPropJustifyContent, flexPropOrder, - type FlexProps, flexPropWrap, -} from './flex'; +} from './index'; +import type { FlexProps } from './index'; const meta = { title: 'Mixins/flex', diff --git a/packages/components/src/components/layout/flex/flex.test.ts b/packages/components/src/components/layout/flex/flex.test.ts new file mode 100644 index 000000000..469b309e6 --- /dev/null +++ b/packages/components/src/components/layout/flex/flex.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { flex } from './flex'; +import s from './flex.module.css'; + +describe('flex', () => { + it('should generate classes for every flex property', () => { + expect( + flex( + { + flex: 'inline-flex', + wrap: 'wrap-reverse', + order: -1, + gap: 'm', + rowGap: 'l', + colGap: 'xl', + direction: 'column-reverse', + alignItems: 'center', + justifyContent: 'space-between', + }, + 'custom-class' + ) + ).toBe( + [ + s.base, + s['flex_inline-flex'], + s['wrap_wrap-reverse'], + s['order_-1'], + s.gap_row_l, + s.gap_column_xl, + s['direction_column-reverse'], + s.alignItems_center, + s['justifyContent_space-between'], + 'custom-class', + ].join(' ') + ); + }); + + it('should use gap as the row and column gap fallback', () => { + expect(flex({ gap: 'm' })).toBe( + [s.base, s.gap_row_m, s.gap_column_m].join(' ') + ); + }); + + it('should generate class for zero order', () => { + expect(flex({ order: 0 })).toBe([s.base, s.order_0].join(' ')); + }); +}); diff --git a/packages/components/src/components/layout/flex/flex.ts b/packages/components/src/components/layout/flex/flex.ts index d94551c04..1f9146d17 100644 --- a/packages/components/src/components/layout/flex/flex.ts +++ b/packages/components/src/components/layout/flex/flex.ts @@ -1,82 +1,7 @@ import { clsx } from '@koobiq/react-core'; import s from './flex.module.css'; - -export const flexPropAlignItems = [ - 'flex-start', - 'flex-end', - 'center', - 'baseline', - 'stretch', -] as const; -export type FlexPropAlignItems = (typeof flexPropAlignItems)[number]; - -export const flexPropGap = [ - '3xs', - 'xxs', - 'xs', - 's', - 'm', - 'l', - 'xl', - 'xxl', - '3xl', - '4xl', - '5xl', - '6xl', - '7xl', -] as const; -export type FlexPropGap = (typeof flexPropGap)[number]; - -export const flexPropJustifyContent = [ - 'flex-start', - 'flex-end', - 'center', - 'space-between', - 'space-around', - 'space-evenly', -] as const; -export type FlexPropJustifyContent = (typeof flexPropJustifyContent)[number]; - -export const flexPropFlex = ['flex', 'inline-flex'] as const; -export type FlexPropFlex = (typeof flexPropFlex)[number]; - -export const flexPropWrap = ['nowrap', 'wrap', 'wrap-reverse'] as const; -export type FlexPropWrap = (typeof flexPropWrap)[number]; - -export const flexPropDirection = [ - 'row', - 'row-reverse', - 'column', - 'column-reverse', -] as const; -export type FlexPropDirection = (typeof flexPropDirection)[number]; - -export const flexPropOrder = [-1, 0, 1] as const; -export type FlexPropOrder = (typeof flexPropOrder)[number]; - -export type FlexProps = { - /** Defines the `gap` property. */ - gap?: FlexPropGap; - /** Defines the `column-gap` property. */ - colGap?: FlexPropGap; - /** Defines the `row-gap` property. */ - rowGap?: FlexPropGap; - /** Defines the `display` property with `flex` or `inline-flex` value. */ - flex?: FlexPropFlex; - /** Defines the `flex-wrap` property. */ - wrap?: FlexPropWrap; - /** Defines the `order` property. */ - order?: FlexPropOrder; - /** Defines the `flex-direction` property. */ - direction?: FlexPropDirection; - /** Defines the `align-items` property. */ - alignItems?: FlexPropAlignItems; - /** Defines the `justify-content` property. */ - justifyContent?: FlexPropJustifyContent; -}; - -export type FlexParams = (props: FlexProps, className?: string) => string; +import type { FlexParams } from './types'; /** The flex mixin turns the element it’s applied to into a flex container. */ export const flex: FlexParams = (props, className) => { @@ -89,11 +14,9 @@ export const flex: FlexParams = (props, className) => { gap, rowGap: rowGapProp, colGap: colGapProp, - order: orderProp, + order, } = props; - const order = String(orderProp); - const colGap = colGapProp ?? gap; const rowGap = rowGapProp ?? gap; @@ -101,7 +24,7 @@ export const flex: FlexParams = (props, className) => { s.base, flex && s[`flex_${flex}`], wrap && s[`wrap_${wrap}`], - order && s[`order_${order}`], + order !== undefined && s[`order_${order}`], rowGap && s[`gap_row_${rowGap}`], colGap && s[`gap_column_${colGap}`], direction && s[`direction_${direction}`], diff --git a/packages/components/src/components/layout/flex/index.ts b/packages/components/src/components/layout/flex/index.ts index ece6890a3..6ef28bf01 100644 --- a/packages/components/src/components/layout/flex/index.ts +++ b/packages/components/src/components/layout/flex/index.ts @@ -1 +1,2 @@ export * from './flex'; +export * from './types'; diff --git a/packages/components/src/components/layout/flex/types.ts b/packages/components/src/components/layout/flex/types.ts new file mode 100644 index 000000000..80bbe14a9 --- /dev/null +++ b/packages/components/src/components/layout/flex/types.ts @@ -0,0 +1,75 @@ +export const flexPropAlignItems = [ + 'flex-start', + 'flex-end', + 'center', + 'baseline', + 'stretch', +] as const; +export type FlexPropAlignItems = (typeof flexPropAlignItems)[number]; + +export const flexPropGap = [ + '3xs', + 'xxs', + 'xs', + 's', + 'm', + 'l', + 'xl', + 'xxl', + '3xl', + '4xl', + '5xl', + '6xl', + '7xl', +] as const; +export type FlexPropGap = (typeof flexPropGap)[number]; + +export const flexPropJustifyContent = [ + 'flex-start', + 'flex-end', + 'center', + 'space-between', + 'space-around', + 'space-evenly', +] as const; +export type FlexPropJustifyContent = (typeof flexPropJustifyContent)[number]; + +export const flexPropFlex = ['flex', 'inline-flex'] as const; +export type FlexPropFlex = (typeof flexPropFlex)[number]; + +export const flexPropWrap = ['nowrap', 'wrap', 'wrap-reverse'] as const; +export type FlexPropWrap = (typeof flexPropWrap)[number]; + +export const flexPropDirection = [ + 'row', + 'row-reverse', + 'column', + 'column-reverse', +] as const; +export type FlexPropDirection = (typeof flexPropDirection)[number]; + +export const flexPropOrder = [-1, 0, 1] as const; +export type FlexPropOrder = (typeof flexPropOrder)[number]; + +export type FlexProps = { + /** Defines the `gap` property. */ + gap?: FlexPropGap; + /** Defines the `column-gap` property. */ + colGap?: FlexPropGap; + /** Defines the `row-gap` property. */ + rowGap?: FlexPropGap; + /** Defines the `display` property with `flex` or `inline-flex` value. */ + flex?: FlexPropFlex; + /** Defines the `flex-wrap` property. */ + wrap?: FlexPropWrap; + /** Defines the `order` property. */ + order?: FlexPropOrder; + /** Defines the `flex-direction` property. */ + direction?: FlexPropDirection; + /** Defines the `align-items` property. */ + alignItems?: FlexPropAlignItems; + /** Defines the `justify-content` property. */ + justifyContent?: FlexPropJustifyContent; +}; + +export type FlexParams = (props: FlexProps, className?: string) => string;