diff --git a/.changeset/brave-singers-knock.md b/.changeset/brave-singers-knock.md new file mode 100644 index 00000000000..7a6da135c91 --- /dev/null +++ b/.changeset/brave-singers-knock.md @@ -0,0 +1,19 @@ +--- +'braid-design-system': minor +--- + +--- +new: + - PageBlock +--- + +**PageBlock:** Add new component + +Provides a top-level page container, constraining the content width (using `ContentBlock`) while establishing common screen gutters on smaller devices. + +**EXAMPLE USAGE:** +```jsx + + ... + +``` diff --git a/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx b/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx index b9fe8d651dc..362ead61449 100644 --- a/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx +++ b/packages/braid-design-system/src/lib/components/ContentBlock/ContentBlock.docs.tsx @@ -21,7 +21,9 @@ const docs: ComponentDocs = { the content it wraps. ), - alternatives: [], + alternatives: [ + { name: 'PageBlock', description: 'For page-level layout blocks' }, + ], additional: [ { label: 'Maximum width', diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx new file mode 100644 index 00000000000..1052ebbeb6c --- /dev/null +++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.docs.tsx @@ -0,0 +1,104 @@ +import React, { Fragment } from 'react'; +import type { ComponentDocs } from 'site/types'; +import { Placeholder } from '../private/Placeholder/Placeholder'; +import { Box, PageBlock, TextLink } from '../'; +import source from '../../utils/source.macro'; +import { Strong } from '../Strong/Strong'; +import { Text } from '../Text/Text'; +import { gutters, validPageBlockComponents } from './PageBlock'; + +const docs: ComponentDocs = { + category: 'Layout', + migrationGuide: true, + Example: () => + source( + + + , + ), + description: ( + + Provides a top-level page container, constraining the content width (using{' '} + ContentBlock) while + establishing common screen gutters on smaller devices. + + ), + alternatives: [ + { + name: 'ContentBlock', + description: 'For controlled width layout blocks', + }, + ], + additional: [ + { + label: 'Maximum width', + description: ( + + Use the width prop to adjust the maximum width of the + page container. Choose from either medium or{' '} + large. + + ), + Example: () => + source( + + + , + ), + }, + { + label: 'Screen gutters', + description: ( + <> + + Establishes consistent responsive gutters between the content and + the screen edge. + + + Uses {gutters.mobile} space on{' '} + mobile and the semantic{' '} + {gutters.tablet} on{' '} + tablet and above. + + + ), + playroom: false, + code: false, + Example: () => + source( + + + + + + + , + ), + }, + + { + label: 'Custom semantics', + description: ( + + The HTML tag can be customised to ensure the underlying document + semantics are meaningful. This can be done using the{' '} + component prop and supports{' '} + {validPageBlockComponents.map((c, i) => { + const notLastTwo = validPageBlockComponents.length - 2; + const joiningLastElements = i === notLastTwo ? ' and ' : '.'; + + return ( + + {c} + {c === 'div' ? ' (default)' : ''} + {i < notLastTwo ? ', ' : joiningLastElements} + + ); + })} + + ), + }, + ], +}; + +export default docs; diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx new file mode 100644 index 00000000000..f6791947a5c --- /dev/null +++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.gallery.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import type { ComponentExample } from 'site/types'; +import { Placeholder } from '../private/Placeholder/Placeholder'; +import { PageBlock } from '../'; +import source from '../../utils/source.macro'; + +export const galleryItems: ComponentExample[] = [ + { + label: 'Medium width', + Example: () => + source( + + + , + ), + }, + { + label: 'Large width', + Example: () => + source( + + + , + ), + }, +]; diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx new file mode 100644 index 00000000000..d0e8c7585e8 --- /dev/null +++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.screenshots.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import type { ComponentScreenshot } from 'site/types'; +import { Placeholder } from '../private/Placeholder/Placeholder'; +import { PageBlock } from '../'; + +export const screenshots: ComponentScreenshot = { + screenshotWidths: [320, 1200], + examples: [ + { + label: 'Default', + Example: () => ( + + + + ), + }, + { + label: 'Medium', + Example: () => ( + + + + ), + }, + { + label: 'Large', + Example: () => ( + + + + ), + }, + ], +}; diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx new file mode 100644 index 00000000000..81b9ea3ce43 --- /dev/null +++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.snippets.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import type { Snippets } from '../private/Snippets'; +import { PageBlock, Placeholder } from '../../playroom/components'; +import source from '../../utils/source.macro'; + +export const snippets: Snippets = [ + { + name: 'Medium', + code: source( + + + , + ), + }, + { + name: 'Large', + code: source( + + + , + ), + }, +]; diff --git a/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx new file mode 100644 index 00000000000..5589c5ec3ab --- /dev/null +++ b/packages/braid-design-system/src/lib/components/PageBlock/PageBlock.tsx @@ -0,0 +1,50 @@ +import React, { type ReactNode } from 'react'; +import { + ContentBlock, + type ContentBlockProps, +} from '../ContentBlock/ContentBlock'; +import { Box } from '../Box/Box'; +import buildDataAttributes, { + type DataAttributeMap, +} from '../private/buildDataAttributes'; + +export const validPageBlockComponents = [ + 'div', + 'article', + 'aside', + 'main', + 'section', + 'nav', +] as const; + +export const gutters = { mobile: 'xsmall', tablet: 'gutter' } as const; + +interface Props { + children: ReactNode; + width?: Extract; + component?: (typeof validPageBlockComponents)[number]; + data?: DataAttributeMap; +} + +export const PageBlock = ({ + children, + width = 'large', + component: componentProp, + data, + ...restProps +}: Props) => { + const component = + componentProp && validPageBlockComponents.includes(componentProp) + ? componentProp + : 'div'; + + return ( + + {children} + + ); +}; diff --git a/packages/braid-design-system/src/lib/components/index.ts b/packages/braid-design-system/src/lib/components/index.ts index cf52a4a581b..ca94951b350 100644 --- a/packages/braid-design-system/src/lib/components/index.ts +++ b/packages/braid-design-system/src/lib/components/index.ts @@ -56,6 +56,7 @@ export { MenuItemLink } from './MenuItem/MenuItemLink'; export { OverflowMenu } from './OverflowMenu/OverflowMenu'; export { MonthPicker } from './MonthPicker/MonthPicker'; export { Notice } from './Notice/Notice'; +export { PageBlock } from './PageBlock/PageBlock'; export { Pagination } from './Pagination/Pagination'; export { PasswordField } from './PasswordField/PasswordField'; export { Radio } from './Radio/Radio'; diff --git a/packages/braid-design-system/src/lib/playroom/snippets.ts b/packages/braid-design-system/src/lib/playroom/snippets.ts index 717f6e788b8..dcd7bda0d3b 100644 --- a/packages/braid-design-system/src/lib/playroom/snippets.ts +++ b/packages/braid-design-system/src/lib/playroom/snippets.ts @@ -24,6 +24,7 @@ import { snippets as Loader } from './snippets/Loader'; import { snippets as MonthPicker } from './snippets/MonthPicker'; import { snippets as Notice } from './snippets/Notice'; import { snippets as OverflowMenu } from './snippets/OverflowMenu'; +import { snippets as PageBlock } from './snippets/PageBlock'; import { snippets as Pagination } from './snippets/Pagination'; import { snippets as PasswordField } from './snippets/PasswordField'; import { snippets as RadioGroup } from './snippets/RadioGroup'; @@ -70,6 +71,7 @@ export default Object.entries({ MonthPicker, Notice, OverflowMenu, + PageBlock, Pagination, PasswordField, RadioGroup, diff --git a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap index 5737c9d59c9..4196660c389 100644 --- a/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap +++ b/packages/generate-component-docs/src/__snapshots__/contract.test.ts.snap @@ -6797,6 +6797,26 @@ exports[`OverflowMenu 1`] = ` } `; +exports[`PageBlock 1`] = ` +{ + exportType: component, + props: { + children: ReactNode + component?: + | "article" + | "aside" + | "div" + | "main" + | "nav" + | "section" + data?: DataAttributeMap + width?: + | "large" + | "medium" +}, +} +`; + exports[`Pagination 1`] = ` { exportType: component, diff --git a/site/src/App/DocNavigation/DocExample.tsx b/site/src/App/DocNavigation/DocExample.tsx index 938ddb34af2..40584b6b01a 100644 --- a/site/src/App/DocNavigation/DocExample.tsx +++ b/site/src/App/DocNavigation/DocExample.tsx @@ -41,7 +41,7 @@ export const DocExample = ({ {value} ) : null} - {codeAsString ? ( + {code !== false && codeAsString ? ( {codeAsString} diff --git a/site/src/App/routes/foundations/layout/layout.tsx b/site/src/App/routes/foundations/layout/layout.tsx index ee0a9a8d95e..e3cd6360f3f 100644 --- a/site/src/App/routes/foundations/layout/layout.tsx +++ b/site/src/App/routes/foundations/layout/layout.tsx @@ -21,6 +21,7 @@ import { Hidden, Strong, Bleed, + PageBlock, } from 'braid-src/lib/components'; import { TextStack } from '../../../TextStack/TextStack'; import Code from '../../../Code/Code'; @@ -85,6 +86,9 @@ const page: Page = { ContentBlock + + PageBlock + Bleed @@ -773,6 +777,41 @@ const page: Page = { + PageBlock + + For top-level sections, in addition to limiting the width of content on + the screen, it is also important to standardise the gutter between + content and the edge of the screen. For this Braid provides the{' '} + PageBlock component, + which defines responsive gutters around a{' '} + ContentBlock. + + + {source( + + + Hello World + + , + )} + + + To standardise our page-level block widths, the{' '} + width{' '} + prop accepts either medium or large. + + + {source( + + + Hello World + + , + )} + + + + Bleed Sometimes it is necessary for a component to extend out into it’s diff --git a/site/src/types.d.ts b/site/src/types.d.ts index 80c0112bb52..325928bc66a 100644 --- a/site/src/types.d.ts +++ b/site/src/types.d.ts @@ -62,7 +62,7 @@ export interface ComponentExample { props: ExampleProps & PlayroomExampleProps, ) => Source; Container?: (props: { children: ReactNode }) => ReactElement; - code?: string; + code?: string | false; showCodeByDefault?: boolean; playroom?: boolean; }