diff --git a/cypress/integration/sitemap-vrt/constants.ts b/cypress/integration/sitemap-vrt/constants.ts index bf38ff5739..d83c38b644 100644 --- a/cypress/integration/sitemap-vrt/constants.ts +++ b/cypress/integration/sitemap-vrt/constants.ts @@ -129,8 +129,12 @@ export const SITEMAP = [ '/introduction/for-designers/design-guidelines/', '/introduction/for-engineers/manual-installation/', '/introduction/for-engineers/quickstart/', - '/patterns/button-vs-anchor/', '/introduction/working-with-us/', + '/page-templates/', + '/page-templates/object-details/', + '/page-templates/objects-list/', + '/page-templates/settings/', + '/patterns/button-vs-anchor/', '/patterns/confirmation/', '/patterns/delete/', '/patterns/data-export/', @@ -140,7 +144,6 @@ export const SITEMAP = [ '/patterns/error-state/', '/patterns/navigation/', '/patterns/notifications-and-feedback/', - '/patterns/object-details/', '/patterns/privacy/', '/patterns/status/', '/primitives/combobox-primitive/', diff --git a/packages/paste-website/scripts/fetch-data.mjs b/packages/paste-website/scripts/fetch-data.mjs index 00accb43e2..ebe1f07758 100644 --- a/packages/paste-website/scripts/fetch-data.mjs +++ b/packages/paste-website/scripts/fetch-data.mjs @@ -41,6 +41,18 @@ const getAllPatterns = async () => { return patterns.map(({fields}) => fields); }; +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type +const getAllPageTemplates = async () => { + const patterns = await systemTable + .select({ + filterByFormula: 'AND({Component Category} = "page_template", Documentation, status, status != "in development")', + sort: [{field: 'Feature'}], + fields: ['Feature', 'status'], + }) + .all(); + return patterns.map(({fields}) => fields); +}; + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type const getAllPackages = async () => { const root = path.resolve(process.cwd(), '../'); @@ -55,6 +67,7 @@ const getAllPackages = async () => { allPastePrimitive: [], allPasteThemePackage: [], allPastePattern: [], + allPastePageTemplate: [], }; packages.forEach(async (packageJson) => { @@ -70,6 +83,9 @@ const getAllPackages = async () => { const patterns = await getAllPatterns(); data.allPastePattern = [...patterns]; + const pageTemplates = await getAllPageTemplates(); + data.allPastePageTemplate = [...pageTemplates]; + await fs.mkdir(dataPath, {recursive: true}, (err) => { if (err) { // eslint-disable-next-line no-console diff --git a/packages/paste-website/src/assets/images/system.png b/packages/paste-website/src/assets/images/system.png new file mode 100644 index 0000000000..4673c7f854 Binary files /dev/null and b/packages/paste-website/src/assets/images/system.png differ diff --git a/packages/paste-website/src/components/component-overview-table/index.tsx b/packages/paste-website/src/components/component-overview-table/index.tsx index 35373e4a20..7fa876fa22 100644 --- a/packages/paste-website/src/components/component-overview-table/index.tsx +++ b/packages/paste-website/src/components/component-overview-table/index.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; import {Box} from '@twilio-paste/box'; import {Table, THead, TBody, Tr, Th, Td} from '@twilio-paste/table'; -import {Popover, PopoverContainer, PopoverButton} from '@twilio-paste/popover'; import {Text} from '@twilio-paste/text'; -import {InformationIcon} from '@twilio-paste/icons/esm/InformationIcon'; import {SuccessIcon} from '@twilio-paste/icons/esm/SuccessIcon'; import {AssetStatus} from '../component-status/AssetStatus'; @@ -46,19 +44,7 @@ const ComponentOverviewTable: React.FC - - - Peer review - - - - - - - A Component/Pattern has been reviewed by the relevant committee(s) and/or peer stakeholders. - - - + Peer review diff --git a/packages/paste-website/src/components/empty-state/InDevelopment.tsx b/packages/paste-website/src/components/empty-state/InDevelopment.tsx index 0587f047b1..2fde9b631c 100644 --- a/packages/paste-website/src/components/empty-state/InDevelopment.tsx +++ b/packages/paste-website/src/components/empty-state/InDevelopment.tsx @@ -11,15 +11,20 @@ import {getHumanizedNameFromPackageName} from '../../utils/RouteUtils'; interface InDevelopmentProps { type?: 'component' | 'primitive' | 'layout'; name: string; + showBreadcrumb?: boolean; } -const InDevelopment: React.FC> = ({type, name}) => { +const InDevelopment: React.FC> = ({type, showBreadcrumb = true, name}) => { return ( <> - - Home - Components - + {showBreadcrumb ? ( + + Home + Components + + ) : ( + <> + )} {getHumanizedNameFromPackageName(name)} diff --git a/packages/paste-website/src/components/homepage/HomeHero.tsx b/packages/paste-website/src/components/homepage/HomeHero.tsx index c7b1930e3f..5d2bbd1e9f 100644 --- a/packages/paste-website/src/components/homepage/HomeHero.tsx +++ b/packages/paste-website/src/components/homepage/HomeHero.tsx @@ -77,18 +77,18 @@ const HomeHero = (): JSX.Element => { New - Our Figma library now updated with variables, + Introducing page templates! event({ category: 'Hero', action: 'click-new-banner', - label: 'Figma Community with variables', + label: 'Page templates announcement', }) } > - check it out! + See more = ({children}) => { + return ( + + {children} + + ); +}; + +const Required: React.FC = ({children}) => { + return ( + + + + Required + {children} + + + + ); +}; + +const Related: React.FC = ({children}) => { + return ( + + + + Related + {children} + + + + ); +}; + +export {Ingredients, Required, Related}; diff --git a/packages/paste-website/src/components/ingredients/ObjectDetailsIngredients.tsx b/packages/paste-website/src/components/ingredients/ObjectDetailsIngredients.tsx new file mode 100644 index 0000000000..8efa1b2dde --- /dev/null +++ b/packages/paste-website/src/components/ingredients/ObjectDetailsIngredients.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import {UnorderedList, ListItem} from '@twilio-paste/list'; +import {Anchor} from '@twilio-paste/anchor'; + +import {Ingredients, Required, Related} from './Ingredients'; + +const ObjectDetailsIngredients: React.FC = () => { + return ( + + + + + Breadcrumb and{' '} + Heading + + + Description List + + + Stack and Grid + + + + + + + Anchor + + + Button and{' '} + Button Group + + + In Page Navigation and{' '} + Tabs + + + Icon: Information + + + + + ); +}; + +export {ObjectDetailsIngredients}; diff --git a/packages/paste-website/src/components/ingredients/ObjectsListIngredients.tsx b/packages/paste-website/src/components/ingredients/ObjectsListIngredients.tsx new file mode 100644 index 0000000000..25dceb89e3 --- /dev/null +++ b/packages/paste-website/src/components/ingredients/ObjectsListIngredients.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import {UnorderedList, ListItem} from '@twilio-paste/list'; +import {Anchor} from '@twilio-paste/anchor'; + +import {Ingredients, Required, Related} from './Ingredients'; + +const ObjectsListIngredients: React.FC = () => { + return ( + + + + + Breadcrumb and{' '} + Heading + + + Data Grid + + + + + + + Anchor + + + Button and{' '} + Button Group + + + In Page Navigation and{' '} + Tabs + + + Paragraph + + + Icons: Export, Filter, More, Search + + + Filter group pattern + + + Select and{' '} + Combobox + + + Separator + + + Input + + + + + Data export pattern + + + + + ); +}; + +export {ObjectsListIngredients}; diff --git a/packages/paste-website/src/components/ingredients/SettingsIngredients.tsx b/packages/paste-website/src/components/ingredients/SettingsIngredients.tsx new file mode 100644 index 0000000000..9a5c4dc245 --- /dev/null +++ b/packages/paste-website/src/components/ingredients/SettingsIngredients.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import {UnorderedList, ListItem} from '@twilio-paste/list'; +import {Anchor} from '@twilio-paste/anchor'; + +import {Ingredients, Required, Related} from './Ingredients'; + +const SettingsIngredients: React.FC = () => { + return ( + + + + + Breadcrumb and{' '} + Heading + + + Form and form elements like{' '} + Input, Checkbox,{' '} + Radio Group,{' '} + Select, Combobox,{' '} + Textarea,{' '} + Date Picker,{' '} + Time Picker,{' '} + Help Text, and Label + + + Button and{' '} + Button Group + + + + + + + Anchor + + + Avatar + + + Badge + + + Callout + + + Card + + + Description List + + + In Page Navigation and{' '} + Tabs + + + Modal + + + Paragraph + + + Separator + + + Switch + + + Icons: Information, Copy, Edit + + + + + ); +}; + +export {SettingsIngredients}; diff --git a/packages/paste-website/src/components/shortcodes/generic-header/index.tsx b/packages/paste-website/src/components/shortcodes/generic-header/index.tsx index 523f529da4..7812aae10e 100644 --- a/packages/paste-website/src/components/shortcodes/generic-header/index.tsx +++ b/packages/paste-website/src/components/shortcodes/generic-header/index.tsx @@ -6,6 +6,7 @@ import {Stack} from '@twilio-paste/stack'; import {Text} from '@twilio-paste/text'; import {Heading} from '@twilio-paste/heading'; import {useTheme} from '@twilio-paste/theme'; +import {Breadcrumb, BreadcrumbItem} from '@twilio-paste/breadcrumb'; import {LinkExternalIcon} from '@twilio-paste/icons/esm/LinkExternalIcon'; import {PackageStatusLegend} from '../package-status-legend'; @@ -96,7 +97,13 @@ const GenericHeader: React.FC> = ({ )} {shouldShowBreadcrumbs && ( - {isFoundations ? <>{categoryName} : {categoryName}} + + {isFoundations ? ( + {categoryName} + ) : ( + {categoryName} + )} + )} diff --git a/packages/paste-website/src/components/shortcodes/normalized-pattern-header/index.tsx b/packages/paste-website/src/components/shortcodes/normalized-pattern-header/index.tsx index 8e42ff8018..9c0ea6a176 100644 --- a/packages/paste-website/src/components/shortcodes/normalized-pattern-header/index.tsx +++ b/packages/paste-website/src/components/shortcodes/normalized-pattern-header/index.tsx @@ -31,7 +31,11 @@ interface NormalizedPatternHeaderProps extends GenericHeaderProps { data: ApiData; } -const NormalizedPatternHeader: React.FC> = ({data, ...props}) => { +const NormalizedPatternHeader: React.FC> = ({ + data, + categoryRoute = SidebarCategoryRoutes.PATTERNS, + ...props +}) => { const normalizedData = getNormalizedHeaderData(data); const { name, @@ -48,7 +52,7 @@ const NormalizedPatternHeader: React.FC { const navigationData = useNavigationContext(); // take airtable feature data and mutate it into navigation data - const {allPasteComponent, allPasteLayout, allPastePrimitive, allPastePattern} = + const {allPasteComponent, allPasteLayout, allPastePrimitive, allPastePattern, allPastePageTemplate} = getNormalizedNavigationData(navigationData); const allComponentSidebarItems = [...allPasteComponent, ...allPasteLayout]; @@ -192,6 +192,14 @@ const SiteSidebarNavigation = (): JSX.Element => { ))} + + Overview + {allPastePageTemplate.map(({name, slug}: {[key: string]: string}) => ( + + {name} + + ))} + -## Related Paste patterns +## Related Paste patterns and page templates - [Create](/patterns/create) - [Notifications and Feedback](/patterns/notifications-and-feedback/) -- [Object detail](/patterns/object-details) +- [Object details](/page-templates/object-details) ## Usage guide diff --git a/packages/paste-website/src/pages/components/index.mdx b/packages/paste-website/src/pages/components/index.mdx index 63071775a2..61fb947216 100644 --- a/packages/paste-website/src/pages/components/index.mdx +++ b/packages/paste-website/src/pages/components/index.mdx @@ -40,6 +40,8 @@ export const getStaticProps = async () => { --- +For more information about how Paste is built and where components fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + diff --git a/packages/paste-website/src/pages/components/meter/index.mdx b/packages/paste-website/src/pages/components/meter/index.mdx index beca24538d..0889ba7932 100644 --- a/packages/paste-website/src/pages/components/meter/index.mdx +++ b/packages/paste-website/src/pages/components/meter/index.mdx @@ -36,7 +36,7 @@ export const getStaticProps = async () => { @@ -63,7 +63,7 @@ A Meter is a visual representation to indicate how full something is. A Meter represents a bucket that can be empty, full, or somewhere in between. Use a Meter when you need to show capacity. For example, use a Meter to show how much data is being used or how many emails were sent successfully. -A Progress Bar (coming soon!) represents **only** task completion, like a file upload or filling out a form. If you’re not displaying progress on a particular task, use a Meter. +A [Progress Bar](/components/progress-bar) represents **only** task completion, like a file upload or filling out a form. If you’re not displaying progress on a particular task, use a Meter. ### Accessibility diff --git a/packages/paste-website/src/pages/components/progress-bar/index.mdx b/packages/paste-website/src/pages/components/progress-bar/index.mdx new file mode 100644 index 0000000000..975eab9d02 --- /dev/null +++ b/packages/paste-website/src/pages/components/progress-bar/index.mdx @@ -0,0 +1,55 @@ +export const meta = { + title: 'Meter', + package: '@twilio-paste/meter', + description: 'Meter is a visual representation of a numerical value within a known range.', + slug: '/components/meter/', +}; + +import Changelog from '@twilio-paste/progress-bar/CHANGELOG.md'; +import packageJson from '@twilio-paste/progress-bar/package.json'; + +import {InDevelopment} from '../../../components/empty-state/InDevelopment'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {SidebarCategoryRoutes} from '../../../constants'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Progress bar'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + }, + }; +}; + + + +--- + + + + + + + + + + + + + + + + diff --git a/packages/paste-website/src/pages/introduction/about-paste/index.mdx b/packages/paste-website/src/pages/introduction/about-paste/index.mdx index 289f654409..0f3e0a1511 100644 --- a/packages/paste-website/src/pages/introduction/about-paste/index.mdx +++ b/packages/paste-website/src/pages/introduction/about-paste/index.mdx @@ -10,6 +10,9 @@ import {UnorderedList, ListItem} from '@twilio-paste/list'; import {Anchor} from '@twilio-paste/anchor'; import {Disclosure, DisclosureHeading, DisclosureContent} from '@twilio-paste/disclosure'; import {InlineCode} from '@twilio-paste/inline-code'; + +import {ResponsiveImage} from '../../../components/ResponsiveImage'; +import System from '../../../assets/images/system.png'; import DefaultLayout from '../../../layouts/DefaultLayout'; import {getNavigationData} from '../../../utils/api'; @@ -42,6 +45,14 @@ export const getStaticProps = async () => { Paste is a design system used to build accessible, cohesive, and high-quality customer experiences at Twilio. Paste supports Product Designers and Engineers with tooling and assets that help them build customer UIs in Figma and React. +## What's a design system? + +Design systems are much more than just component libraries. Paste combines Twilio’s design language, via our foundations on typography, color, layout, and spacing, with content design guidelines, accessible functional primitives, themes, and interactive UI components—everything you need to create high quality, accessible software applications. It ensures each product resonates with our customers, and presents Twilio in a consistent and cohesive way, that is backed by customer research. + + + +[Themes](/theme) in Paste are built on top of [design tokens](/tokens) and define what style each token represents in that theme. When a theme is paired with functional [primitives](/primitives) like Box and Text, they create themed Paste [components](/components) like Heading, Card, and Button. [Patterns](/patterns), like empty state, are composed using a group of components. Multiple patterns are then used to compose full [page templates](/page-templates) like an objects list. + ## Paste is... - **Accessible by default.** As a principle, the design systems team does not ship a component, primitive, or composition if it does not meet or surpass our target of WCAG 2.1 AA Compliance. This means that as a consuming team, you can expect a lot of the heavy lifting required to build accessible products to be done by Paste. However, you must still think about, and regularly audit your product for accessibility. The Design Systems team can support you on this work. diff --git a/packages/paste-website/src/pages/page-templates/index.mdx b/packages/paste-website/src/pages/page-templates/index.mdx new file mode 100644 index 0000000000..a4d5c28da3 --- /dev/null +++ b/packages/paste-website/src/pages/page-templates/index.mdx @@ -0,0 +1,56 @@ +export const meta = { + title: 'Page templates', + description: 'overview page', + slug: '/page-templates/', +}; + +import {Anchor} from '@twilio-paste/anchor'; +import {Box} from '@twilio-paste/box'; +import {Heading} from '@twilio-paste/heading'; +import {Paragraph} from '@twilio-paste/paragraph'; +import {Text} from '@twilio-paste/text'; +import {Separator} from '@twilio-paste/separator'; +import {SiteLink} from '../../components/SiteLink.tsx'; +import {Breadcrumb, BreadcrumbItem} from '../../components/breadcrumb'; +import {SidebarCategoryRoutes} from '../../constants'; +import {ComponentOverviewTable} from '../../components/component-overview-table'; +import DefaultLayout from '../../layouts/DefaultLayout'; +import {getAllComponents, getNavigationData} from '../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const components = await getAllComponents(['page template']); + const navigationData = await getNavigationData(); + return { + props: { + componentList: components, + navigationData, + }, + }; +}; + + + + + Home + + + {meta.title} + + + + +--- + +## What is a page template? + +Page templates bring a collection of [components](/components) and [patterns](/patterns) together into recommended full-page layouts to drive further cohesion across our platforms. + +Their purpose is more for inspiration rather than being strict spacing guidelines to align all pages. However, some templates will still have user experience guidelines to follow to create the ideal flow for customers. + +Paste page templates are product- and platform-agnostic, but subsystems and specific platforms may find it useful to create their own page templates for specific customer use cases. + +For more information about how Paste is built and where page templates fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + + diff --git a/packages/paste-website/src/pages/page-templates/object-details/index.mdx b/packages/paste-website/src/pages/page-templates/object-details/index.mdx new file mode 100644 index 0000000000..9e89412a02 --- /dev/null +++ b/packages/paste-website/src/pages/page-templates/object-details/index.mdx @@ -0,0 +1,104 @@ +export const meta = { + title: 'Object details', + description: + 'The Object details page template allows customers to view and take action on properties of a single object.', + slug: '/page-templates/object-details/', +}; + +import {ObjectDetailsIngredients} from '../../../components/ingredients/ObjectDetailsIngredients'; +import {SidebarCategoryRoutes} from '../../../constants'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Object details'); + return { + props: { + data: { + ...feature, + }, + navigationData, + }, + }; +}; + + + + + + +--- + + + + + + + +## About Object details + +Object details give customers a primarily read-only view of the properties of an object, such as a message log or sent email. + +If the customer is primarily editing information, use the [Settings](/page-templates/settings) page instead. + +## Accessibility + +A well-structured [document hierarchy](https://www.w3.org/WAI/tutorials/page-structure/) helps provide efficient in-page navigation for keyboard users, assistive technologies, and mobile web users. + +Proper hierarchy allows users to skip directly to content that is most relevant to them. This is especially important on an Object details page where there may be many different types of information and customers are more likely to skim through. + +To structure a page: + +- Use [Headings](/components/heading) to organize the page. +- Use Headings in sequential order. For example, don’t place an H4 directly after an H2. +- Use sentence case for headers (“Message date” not “Message Date”). +- Use a [Description List](/components/description-list) to define relationships between a property and its label. +- Use [Tables](/components/table) to organize data that are meant to be compared. + +## Ingredients + + + +## How to use this template + +### Page header + +At the top of the page, add a page heading using the format “[Object name] details” for a generic object, or the name of the object if it’s unique or set by the customer. For example, “Call details” or “test audience.” + +The full header should be responsive to the width of the browser window. Heading and Paragraph should have a max width of `$size-80`. + +### Body + +Object details should allow a customer to easily scan for the information they’re looking for. Show an overview of an object’s details with a [Description List](/components/description-list). If there are more than 6 properties, divide the properties into 2 columns. + +Below this, group more detailed properties into sections on the page or, if necessary, into [Tabs](/components/tabs). + +Use customer research to inform how to order the content sections on a details page. Show customers the most relevant information first. Use sentence case for property labels (“End time” not “End Time”). + +The body should be full width and responsive to the size of the window, while Headings, Paragraphs, and Description Lists should have a max width of `$size-80`. + +## Starter kits + +### CodeSandbox + +Coming soon + +### Figma + +Add the “[Paste Patterns & Templates](https://www.figma.com/file/S4z0Kqjb9AYosnkRQldLte/Paste-Patterns-%26-Templates?type=design&node-id=1-4&mode=design&t=dC1B5k0lVIB0JIuX-0)” library to your file. + +## Related discussions + +- [Label Value styling](https://github.com/twilio-labs/paste/discussions/528) + + + + diff --git a/packages/paste-website/src/pages/page-templates/objects-list/index.mdx b/packages/paste-website/src/pages/page-templates/objects-list/index.mdx new file mode 100644 index 0000000000..4261b39329 --- /dev/null +++ b/packages/paste-website/src/pages/page-templates/objects-list/index.mdx @@ -0,0 +1,92 @@ +export const meta = { + title: 'Objects list', + description: + 'The Objects list page template shows a list of unique items for when a customer needs to perform an action on the items.', + slug: '/page-templates/objects-list/', +}; + +import {ObjectsListIngredients} from '../../../components/ingredients/ObjectsListIngredients'; +import {SidebarCategoryRoutes} from '../../../constants'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Objects list'); + return { + props: { + data: { + ...feature, + }, + navigationData, + }, + }; +}; + + + + + + + +--- + + + + + + + +## About Objects list + +An Objects list shows a list of unique items or records (objects), such as logs or contacts. The page can include [search and filtering](/patterns/filter-group) to help customers find an item they’re looking for, and bulk actions like [export](/patterns/data-export) or [delete](/patterns/delete). + +Use an Objects list when a customer needs to: + +- View objects of a single type +- Find specific objects quickly so they can view more [object details](/page-templates/object-details), edit, or delete them +- Create a new object of this type +- Perform bulk actions on multiple objects + +If the customer is primarily editing information, use the [Settings](/page-templates/settings) page instead. + +## Ingredients + + + +## How to use this template + +### Page header + +At the top of the page, use the type of object as the page heading. Use the primary action in the header only for adding a new object. Optionally, use text under the heading to explain the primary use of the page. + +The full header should be responsive to the width of the browser window. Heading and Paragraph should have a max width of `$size-70`. + +### Body + +The rest of the page follows the [Filter Group pattern](/patterns/filter-group) and uses a [Data Grid](/components/data-grid). If there’s already a primary action in the page header, make the filter button a secondary button. If there are no interactive cells, use a Table instead of a Data Grid. + +If the customer needs to examine each object in more detail, link each object to its [Object details](/page-templates/object-details) page. + +The body should be responsive to the width of the browser window. + +## Starter kits + +### CodeSandbox + +Coming soon + +### Figma + +Add the “[Paste Patterns & Templates](https://www.figma.com/file/S4z0Kqjb9AYosnkRQldLte/Paste-Patterns-%26-Templates?type=design&node-id=5310-79458&mode=design)” library to your file. + + + + diff --git a/packages/paste-website/src/pages/page-templates/settings/index.mdx b/packages/paste-website/src/pages/page-templates/settings/index.mdx new file mode 100644 index 0000000000..a31b256ea4 --- /dev/null +++ b/packages/paste-website/src/pages/page-templates/settings/index.mdx @@ -0,0 +1,98 @@ +export const meta = { + title: 'Settings', + description: 'The Settings page template lets customers edit information and update simple settings.', + slug: '/page-templates/settings/', +}; + +import {SettingsIngredients} from '../../../components/ingredients/SettingsIngredients'; +import {SidebarCategoryRoutes} from '../../../constants'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Settings'); + return { + props: { + data: { + ...feature, + }, + navigationData, + }, + }; +}; + + + + + + + +--- + + + + + + + +## About Settings + +Settings pages let customers edit information and update simple settings. + +If a customer is creating an object or the editing process is complex and requires additional guidance or focus, use the [create](/patterns/create) pattern and [wizard](/page-templates/wizard) template instead. + +A Settings page can be a standalone page, or you can use a Settings overview page that links to multiple Settings pages. + +## Ingredients + + + +## How to use this template + +### Page header + +At the top of the page, add a page heading using the format “[Settings type] settings.” For example, “Workplace settings” or “Source settings.” Optionally, use text under the heading to explain the primary settings on the page. + +Heading and Paragraph should have a max width of `$size-70`. + +### Body + +Like [Object details](/page-templates/object-details), Settings should also allow a customer to easily scan for the information they’re looking to edit. Group related settings under Headings. Use sentence case for all Headings (“Billing details” not “Billing Details”). + +If a group of settings needs additional explanation, use a Paragraph under a single Heading. If a single setting needs additional explanation, use Help Text with its form field. + +Use customer research to inform how to order sections on the Settings page. + +### Settings overview page + +If there are too many settings to fit on a single page, split related settings into separate pages or Modals, and link to them from a Settings overview page. On a Settings overview page, use [Cards](/components/card) to group sections and surface the most relevant information with [Description Lists](components/description-list). + +Position the page content in the center with a max width of `$size-90`. + +### Using Switches + +If all settings on a page have only “on/off” or “yes/no” states, you can use Switches. + +However when you’re using a Switch, all settings on that page _must_ use Switches because their interaction result is immediate. Use a [Toast](/components/toast) to confirm the result. No “Save” Button is needed. + +## Starter kits + +### CodeSandbox + +Coming soon + +### Figma + +Add the “[Paste Patterns & Templates](https://www.figma.com/file/S4z0Kqjb9AYosnkRQldLte/Paste-Patterns-%26-Templates?type=design&node-id=367-27339&mode=design&t=dC1B5k0lVIB0JIuX-0)” library to your file. + + + + diff --git a/packages/paste-website/src/pages/page-templates/wizard/index.mdx b/packages/paste-website/src/pages/page-templates/wizard/index.mdx new file mode 100644 index 0000000000..fae0e7712d --- /dev/null +++ b/packages/paste-website/src/pages/page-templates/wizard/index.mdx @@ -0,0 +1,50 @@ +export const meta = { + title: 'Wizard', + description: 'A Wizard flow guides customers through complicated tasks to reach a goal.', + slug: '/page-templates/wizard/', +}; + +import {InDevelopment} from '../../../components/empty-state/InDevelopment'; +import {SidebarCategoryRoutes} from '../../../constants'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Wizard'); + return { + props: { + data: { + ...feature, + }, + navigationData, + }, + }; +}; + + + + + + + +--- + + + + + + + + + + + + diff --git a/packages/paste-website/src/pages/patterns/index.mdx b/packages/paste-website/src/pages/patterns/index.mdx index 4740b128ed..7e572e191f 100644 --- a/packages/paste-website/src/pages/patterns/index.mdx +++ b/packages/paste-website/src/pages/patterns/index.mdx @@ -58,6 +58,8 @@ There are two primary goals of patterns: - To drive efficiency across Twilio product teams by ensuring that no one needs to solve the same problem twice. - To establish cohesion across the full Twilio platform so that users do not need to re-learn how to perform certain actions as they move from product to product. +For more information about how Paste is built and where patterns fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + ## Patterns diff --git a/packages/paste-website/src/pages/patterns/object-details/index.mdx b/packages/paste-website/src/pages/patterns/object-details/index.mdx deleted file mode 100644 index 77c53846ee..0000000000 --- a/packages/paste-website/src/pages/patterns/object-details/index.mdx +++ /dev/null @@ -1,194 +0,0 @@ -export const meta = { - title: 'Object details', - description: 'The Object Details pattern displays a read-only page or panel containing object attributes.', - slug: '/patterns/object-details/', -}; - -import {Anchor} from '@twilio-paste/anchor'; -import {Box} from '@twilio-paste/box'; -import {Callout, CalloutHeading, CalloutText} from '@twilio-paste/callout'; -import {Card} from '@twilio-paste/card'; -import {Disclosure, DisclosureHeading, DisclosureContent} from '@twilio-paste/disclosure'; -import {Grid, Column} from '@twilio-paste/grid'; -import {Heading} from '@twilio-paste/heading'; -import {Paragraph} from '@twilio-paste/paragraph'; -import {Stack} from '@twilio-paste/stack'; -import {Text} from '@twilio-paste/text'; -import {ResponsiveImage} from '../../../components/ResponsiveImage'; -import ObjectDetailsStandardImage from '../../../assets/images/patterns/object-details-standard@3x.png'; -import ObjectDetailsStandardReorderedImage from '../../../assets/images/patterns/object-details-standard-reordered@3x.png'; -import ObjectDetailsTabbedImage from '../../../assets/images/patterns/object-details-tabbed@3x.png'; -import DefaultLayout from '../../../layouts/DefaultLayout'; -import {getFeature, getNavigationData} from '../../../utils/api'; - -export default DefaultLayout; - -export const getStaticProps = async () => { - const navigationData = await getNavigationData(); - const feature = await getFeature('Object details'); - return { - props: { - data: { - ...feature, - }, - navigationData, - }, - }; -}; - - - - - - - ---- - - - - - - - -## Ingredients - - - - - - - Heading - - - - - Tabs - - - - - Text - - - - - - - - - Box - - - - - Stack - - - - - - - - - Description List - - - - - Grid - - - - - - -```jsx -// import all components for Object details patterns - -import {​ Heading } from "@twilio-paste/core/heading"; -import { Tabs, TabList, Tab, TabPanels, TabPanel } from "@twilio-paste/core/tabs"; -import {​ Text } from "@twilio-paste/core/text"; -import { Box } from "@twilio-paste/core/box"; -import { Stack } from "@twilio-paste/core/stack"; -import { DescriptionList } from "@twilio-paste/core/description-list"; -import { Grid, Column } from "@twilio-paste/core/grid"; -``` - -## Usage - -## General usage - -An object details page or panel gives a customer a read-only view of their information, such as a message log or sent email. - -Object details should: - -- Be clearly separated into content sections with headings, and if necessary, tabs to allow a customer to easily scan for relevant information. -- Prioritize the order of content based on what customers find most important from customer research. - -### Accessibility - -A well-structured [document hierarchy](https://www.w3.org/WAI/tutorials/page-structure/) helps provide efficient in-page navigation for keyboard users, assistive technologies, and mobile web users. - -Proper hierarchy allows users to skip directly to content that is most relevant to them. This is especially important on an object details page where there may be many different types of information and customers are more likely to skim through. - -To structure a page: - -- Use headings to organize the page. -- Use headings in sequential order. For example, don’t place an H4 directly after an H2. -- Use description lists to define relationships between a property and its label. -- Use tables to organize data that are meant to be compared. - -## Variations - -### Standard details - -The most common way to structure an object details page is to first show an overview of properties in a [Description List](/components/description-list), followed by more detailed content sections. Properties should be left-aligned, and property labels and values should be vertically placed. - - - -If there are more than 6 properties, divide the properties into 2 columns. - -While this might be the most common structure for object details, use customer research to inform how to order the content sections on a details page. Show customers the most relevant information first. - -For example, when viewing the details of a sent email campaign, a customer may want to quickly see how well their campaign has been performing over time. In this case, placing a group of stats cards and a chart first may be most appropriate. - - - -### Tabbed details - -When you find that customers want to see multiple, top-level content sections, separate them with [Tabs](/components/tabs). - - - -### Placement of object actions - -Many object detail pages also include a set of actions that can be performed on that object, such as editing or deleting. - - - This pattern is coming soon! - - We've not yet patternized this part of Object Details. If you need this for your current or upcoming work, please - consider partnering with us to contribute it. - - - -## Starter kits - -### CodeSandbox - -Coming soon - -### Figma - -Coming soon - -## Related discussions - -- [Label Value styling](https://github.com/twilio-labs/paste/discussions/528) - - - - diff --git a/packages/paste-website/src/pages/primitives/index.mdx b/packages/paste-website/src/pages/primitives/index.mdx index c7f27b9023..b218b1c1be 100644 --- a/packages/paste-website/src/pages/primitives/index.mdx +++ b/packages/paste-website/src/pages/primitives/index.mdx @@ -37,6 +37,8 @@ export const getStaticProps = async () => { --- +For more information about how Paste is built and where primitives fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + diff --git a/packages/paste-website/src/pages/theme/index.mdx b/packages/paste-website/src/pages/theme/index.mdx index 863485353e..5772c1fb0d 100644 --- a/packages/paste-website/src/pages/theme/index.mdx +++ b/packages/paste-website/src/pages/theme/index.mdx @@ -45,6 +45,8 @@ export const getStaticProps = async () => { This package allows for straight-forward access to Paste design tokens via a Theme Provider to React components. By using this package, you can express your UIs with each of our supported themes, and get their associated token values. +For more information about how Paste is built and where themes fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + ## Theme Paste components are built using our Styling Library, which currently wraps [Emotion](https://emotion.sh/) and diff --git a/packages/paste-website/src/pages/tokens/index.mdx b/packages/paste-website/src/pages/tokens/index.mdx index 2ad17aef7c..b8b13ef929 100644 --- a/packages/paste-website/src/pages/tokens/index.mdx +++ b/packages/paste-website/src/pages/tokens/index.mdx @@ -70,6 +70,8 @@ All Paste components use design tokens. Additionally, [styling primitives with t +For more information about how Paste is built and where tokens fit into that hierarchy, read [About Paste](/introduction/about-paste#whats-a-design-system). + ## Token Naming Design tokens represent fundamental decisions of Paste’s design language, specifying their intended usage with their semantic naming in a predictable and clear way. For example, **`$color-text-link-destructive`** is used for displaying link text that communicates an action is destructive. Using the appropriate token ensures that all destructive actions are styled consistently, appearing as red text in this case. Once a token name is created, it can never change, or be removed. diff --git a/packages/paste-website/src/utils/DataUtils.ts b/packages/paste-website/src/utils/DataUtils.ts index a5913b1e93..64e60743ce 100644 --- a/packages/paste-website/src/utils/DataUtils.ts +++ b/packages/paste-website/src/utils/DataUtils.ts @@ -18,6 +18,7 @@ type NavData = { allPastePrimitive: NavItem[]; allPasteLayout: NavItem[]; allPastePattern: NavItem[]; + allPastePageTemplate: NavItem[]; }; export const getNormalizedHeaderData = (data: ApiData): ApiData => { @@ -61,6 +62,7 @@ export const getNormalizedNavigationData = (data: Feature[]): NavData => { allPasteLayout: [], allPastePrimitive: [], allPastePattern: [], + allPastePageTemplate: [], }; if (data.length === 0) return normalizedData; @@ -82,6 +84,9 @@ export const getNormalizedNavigationData = (data: Feature[]): NavData => { case 'pattern': normalizedData.allPastePattern.push(mutateFeatureToPattern(feature)); break; + case 'page template': + normalizedData.allPastePageTemplate.push(mutateFeatureToPackage(feature)); + break; } }); return normalizedData; diff --git a/packages/paste-website/src/utils/RouteUtils.ts b/packages/paste-website/src/utils/RouteUtils.ts index 0b2b3d2d1b..3268f20938 100644 --- a/packages/paste-website/src/utils/RouteUtils.ts +++ b/packages/paste-website/src/utils/RouteUtils.ts @@ -68,6 +68,8 @@ export const getCategoryNameFromRoute = (categoryRoute: string): string => { return 'Patterns'; case SidebarCategoryRoutes.THEME: return 'Theme'; + case SidebarCategoryRoutes.PAGE_TEMPLATES: + return 'Page templates'; default: return 'Components'; } diff --git a/packages/paste-website/src/utils/__tests__/DataUtils.spec.ts b/packages/paste-website/src/utils/__tests__/DataUtils.spec.ts index ff17b72ea2..0d8580d33a 100644 --- a/packages/paste-website/src/utils/__tests__/DataUtils.spec.ts +++ b/packages/paste-website/src/utils/__tests__/DataUtils.spec.ts @@ -70,13 +70,30 @@ const mockNavData = [ 'Component Category': 'component', Code: 'yes', }, + { + Feature: 'Settings', + status: 'production', + Figma: 'yes', + 'Design committee review': 'no', + 'Engineer committee review': 'no', + Documentation: true, + 'Component Category': 'page template', + Code: 'not applicable', + 'Product suitability': ['Console', 'Flex', 'SendGrid'], + }, ]; describe('getNormalizedNavigationData', () => { test('should return an empty object if the input data is empty', () => { const data: [] = []; const result = getNormalizedNavigationData(data); - expect(result).toEqual({allPasteComponent: [], allPasteLayout: [], allPastePrimitive: [], allPastePattern: []}); + expect(result).toEqual({ + allPasteComponent: [], + allPasteLayout: [], + allPastePrimitive: [], + allPastePattern: [], + allPastePageTemplate: [], + }); }); test('should flatten and mutate the input data correctly for allPasteComponent', () => { @@ -114,6 +131,14 @@ describe('getNormalizedNavigationData', () => { slug: 'combobox-primitive', }, ], + allPastePageTemplate: [ + { + name: 'Settings', + packageName: '@twilio-paste/settings', + packageStatus: 'production', + slug: 'settings', + }, + ], }); }); @@ -131,6 +156,12 @@ describe('getNormalizedNavigationData', () => { }, ]; const result = getNormalizedNavigationData(data); - expect(result).toEqual({allPasteComponent: [], allPasteLayout: [], allPastePrimitive: [], allPastePattern: []}); + expect(result).toEqual({ + allPasteComponent: [], + allPasteLayout: [], + allPastePrimitive: [], + allPastePattern: [], + allPastePageTemplate: [], + }); }); }); diff --git a/packages/paste-website/src/utils/api.ts b/packages/paste-website/src/utils/api.ts index 459dcd2a18..1f85c68f5e 100644 --- a/packages/paste-website/src/utils/api.ts +++ b/packages/paste-website/src/utils/api.ts @@ -31,6 +31,10 @@ export type PastePackages = { Feature: string; status: string; }[]; + allPastePageTemplate: { + Feature: string; + status: string; + }[]; }; export type Feature = { @@ -84,7 +88,7 @@ export const getAllComponents = async (categories: string[]): Promise }; export const getNavigationData = async (): Promise => { - return getAllComponents(['component', 'layout', 'pattern', 'primitive']); + return getAllComponents(['component', 'layout', 'pattern', 'primitive', 'page template']); }; export const getFeature = async (feature: string): Promise => {