diff --git a/packages/epo-react-lib/src/atomic/Center/Center.stories.tsx b/packages/epo-react-lib/src/atomic/Center/Center.stories.tsx new file mode 100644 index 00000000..5dd03242 --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Center/Center.stories.tsx @@ -0,0 +1,67 @@ +import { Meta, StoryFn, StoryObj } from "@storybook/react"; + +import Center from "."; +import { FunctionComponent } from "react"; +import Stack from "../Stack"; + +const meta: Meta = { + component: Center, + argTypes: { + maxWidth: { control: "text" }, + andText: { control: "boolean" }, + intrinsic: { control: "boolean" }, + gutter: { control: "text" }, + }, +}; +export default meta; + +const Iterator: FunctionComponent<{ + backgroundColor?: string; + iterables: Array>; +}> = ({ backgroundColor = "var(--color-rubin-teal-400)", iterables }) => { + return iterables.map((iteration, i) => { + const hasChildren = Array.isArray(iteration); + return ( +
+ {hasChildren ? ( + + ) : ( + iteration + )} +
+ ); + }); +}; + +const Template: StoryFn = (args) => { + const iterables = [1, 2, 3, 4, 5, 6]; + + return ( +
+ + + +
+ ); +}; + +export const Primary: StoryObj = Template.bind({}); + +Primary.args = { + maxWidth: "60ch", +}; diff --git a/packages/epo-react-lib/src/atomic/Center/index.tsx b/packages/epo-react-lib/src/atomic/Center/index.tsx new file mode 100644 index 00000000..2ba6d9ad --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Center/index.tsx @@ -0,0 +1,38 @@ +import { FunctionComponent, PropsWithChildren } from "react"; +import * as Styled from "./styles"; + +export interface CenterProps { + /** The maximum width of the centered element */ + maxWidth?: string; + /** Whether to apply `text-align: center` too */ + andText?: boolean; + /** The width of the gutters (leave empty for no gutters) */ + gutter?: string; + /** Whether to center and child elements narrower than the max value */ + intrinsic?: boolean; + className?: string; +} + +const Center: FunctionComponent> = ({ + maxWidth = "auto", + andText, + gutter = "0", + intrinsic, + className, + children, +}) => { + return ( + + {children} + + ); +}; + +Center.displayName = "Atom.Center"; + +export default Center; diff --git a/packages/epo-react-lib/src/atomic/Center/styles.ts b/packages/epo-react-lib/src/atomic/Center/styles.ts new file mode 100644 index 00000000..19bd428b --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Center/styles.ts @@ -0,0 +1,19 @@ +"use client"; +import styled from "styled-components"; + +export const Center = styled.div` + box-sizing: content-box; + margin-inline: auto; + max-inline-size: var(--size-width-center); + padding-inline: var(--size-gutters); + + &[data-center-text="true"] { + text-align: center; + } + + &[data-intrinsic="true"] { + display: flex; + flex-direction: column; + align-items: center; + } +`; diff --git a/packages/epo-react-lib/src/atomic/Stack/Stack.stories.tsx b/packages/epo-react-lib/src/atomic/Stack/Stack.stories.tsx new file mode 100644 index 00000000..293de24a --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Stack/Stack.stories.tsx @@ -0,0 +1,54 @@ +import { Meta, StoryFn, StoryObj } from "@storybook/react"; + +import Stack from "."; +import { FunctionComponent } from "react"; + +const meta: Meta = { + component: Stack, +}; +export default meta; + +const Iterator: FunctionComponent<{ + backgroundColor?: string; + iterables: Array>; +}> = ({ backgroundColor = "var(--color-rubin-teal-400)", iterables }) => { + return iterables.map((iteration, i) => { + const hasChildren = Array.isArray(iteration); + return ( +
+ {hasChildren ? ( + + ) : ( + iteration + )} +
+ ); + }); +}; + +const Template: StoryFn = (args) => { + const iterables = [1, 2, 3, [3.1, 3.2], 4, 5, 6]; + + return ( + + + + ); +}; + +export const Primary: StoryObj = Template.bind({}); diff --git a/packages/epo-react-lib/src/atomic/Stack/index.tsx b/packages/epo-react-lib/src/atomic/Stack/index.tsx new file mode 100644 index 00000000..2c0a9bf9 --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Stack/index.tsx @@ -0,0 +1,33 @@ +import { FunctionComponent, PropsWithChildren } from "react"; +import * as Styled from "./styles"; + +export interface StackProps { + /** A CSS `margin` value */ + space?: string; + /** Whether the spaces apply recursively (i.e. regardless of nesting level) */ + recursive?: boolean; + className?: string; +} + +const Stack: FunctionComponent> = ({ + space = "var(--size-spacing-s)", + recursive = false, + className, + children, +}) => { + return ( + + {children} + + ); +}; + +Stack.displayName = "Atom.Stack"; + +export default Stack; diff --git a/packages/epo-react-lib/src/atomic/Stack/styles.ts b/packages/epo-react-lib/src/atomic/Stack/styles.ts new file mode 100644 index 00000000..1b555806 --- /dev/null +++ b/packages/epo-react-lib/src/atomic/Stack/styles.ts @@ -0,0 +1,28 @@ +"use client"; +import styled from "styled-components"; + +export const Stack = styled.div` + display: flex; + flex-direction: column; + justify-content: flex-start; + + &[data-recursive="true"] { + * { + margin-block: 0; + } + + * + * { + margin-block-start: var(--size-spacing-stack, 1rem); + } + } + + &[data-recursive="false"] { + > * { + margin-block: 0; + } + + > * + * { + margin-block-start: var(--size-spacing-stack, 1rem); + } + } +`; diff --git a/packages/epo-react-lib/src/index.ts b/packages/epo-react-lib/src/index.ts index 83c766e8..5cee6a0f 100644 --- a/packages/epo-react-lib/src/index.ts +++ b/packages/epo-react-lib/src/index.ts @@ -26,6 +26,7 @@ export { default as ResponsiveImage } from "@/atomic/ResponsiveImage"; export * from "@/atomic/Share"; export { default as Toast } from "@/atomic/Toast"; export { default as Video } from "@/atomic/Video"; +export { default as Stack } from "@/atomic/Stack"; // Content Blocks export { default as SimpleTable } from "@/content-blocks/SimpleTable"; diff --git a/packages/epo-react-lib/vite.config.mts b/packages/epo-react-lib/vite.config.mts index 802e28a3..8ff2d105 100644 --- a/packages/epo-react-lib/vite.config.mts +++ b/packages/epo-react-lib/vite.config.mts @@ -9,7 +9,7 @@ import postcss from "./postcss.config"; const defaultFormat = "es"; const packages = Object.fromEntries( - sync(["src/molecules/**/*.tsx"], { + sync(["src/atomic/*/index.tsx", "src/molecules/*/*.tsx"], { ignore: ["src/**/*.stories.tsx", "src/**/*.test.tsx"], }).map((file) => { const path = resolve(__dirname, file); @@ -32,7 +32,6 @@ const entry = { ExpandToggle: resolve(__dirname, "src/atomic/ExpandToggle/ExpandToggle.tsx"), ExternalLink: resolve(__dirname, "src/atomic/ExternalLink/ExternalLink.tsx"), Figure: resolve(__dirname, "src/atomic/Figure/Figure.tsx"), - Image: resolve(__dirname, "src/atomic/Image/index.tsx"), Link: resolve(__dirname, "src/atomic/Link/Link.tsx"), MixedLink: resolve(__dirname, "src/atomic/MixedLink/MixedLink.tsx"), ProgressBar: resolve(__dirname, "src/atomic/Progress/Bar/ProgressBar.tsx"), @@ -47,7 +46,6 @@ const entry = { ), Share: resolve(__dirname, "src/atomic/Share"), Toast: resolve(__dirname, "src/atomic/Toast/Toast.tsx"), - Video: resolve(__dirname, "src/atomic/Video/index.tsx"), SimpleTable: resolve( __dirname, "src/content-blocks/SimpleTable/SimpleTable.tsx" @@ -77,8 +75,6 @@ const entry = { SlideoutMenu: resolve(__dirname, "src/layout/SlideoutMenu"), IconComposer: resolve(__dirname, "src/svg/IconComposer/index.tsx"), icons: resolve(__dirname, "src/svg/icons"), - Slideout: resolve(__dirname, "src/atomic/Slideout/index.tsx"), - Picture: resolve(__dirname, "src/atomic/Picture/index.tsx"), ...packages, }; @@ -136,6 +132,7 @@ export default defineConfig({ external: [ "@castiron/style-mixins", "@headlessui/react", + "classnames", "flickity", "focus-trap", "i18next",