diff --git a/.changeset/sixty-taxis-watch.md b/.changeset/sixty-taxis-watch.md new file mode 100644 index 0000000000..f451cc149b --- /dev/null +++ b/.changeset/sixty-taxis-watch.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/status': patch +'@twilio-paste/core': patch +--- + +[Status] paired iconography should be decorative and paired with visible, descriptive text diff --git a/cypress/integration/sitemap-vrt/constants.ts b/cypress/integration/sitemap-vrt/constants.ts index f17c50b6bf..542fef0bc7 100644 --- a/cypress/integration/sitemap-vrt/constants.ts +++ b/cypress/integration/sitemap-vrt/constants.ts @@ -15,6 +15,7 @@ export const SITEMAP = [ '/blog/2023-01-27-paste-newsletter/', '/blog/2023-04-13-paste-newsletter/', '/blog/2023-06-12-paste-newsletter/', + '/components/account-switcher/', '/components/aspect-ratio/', '/components/anchor/', '/components/alert-dialog/', @@ -61,6 +62,7 @@ export const SITEMAP = [ '/components/menu/', '/components/paragraph/', '/components/popover/', + '/components/product-switcher/', '/components/radio-group/', '/components/radio-button-group/', '/components/screen-reader-only/', diff --git a/internal-docs/engineering/developer-workflow.md b/internal-docs/engineering/developer-workflow.md index 84d684506d..6c951319ca 100644 --- a/internal-docs/engineering/developer-workflow.md +++ b/internal-docs/engineering/developer-workflow.md @@ -30,7 +30,7 @@ All our work is stored in Jira and we have an [active sprint](https://issues.cor We work in 2 week sprints. -Pick a ticket that you feel comfortable working on, assign it to yourself to let others know you’re working on it, and pull it into the “In Progress” column. Try to limit in-progress tickets to 1 or 2 at a time. +Pick a ticket that you feel comfortable working on, assign it to yourself to let others know you’re working on it, and pull it into the “In-progress” column. Try to limit in-progress tickets to 1 or 2 at a time. ## Branching diff --git a/packages/paste-core/components/status/src/constants.tsx b/packages/paste-core/components/status/src/constants.tsx index a71e22ad9e..c6ee238a92 100644 --- a/packages/paste-core/components/status/src/constants.tsx +++ b/packages/paste-core/components/status/src/constants.tsx @@ -17,78 +17,62 @@ import type {StatusBadges} from './types'; export const StatusObject: StatusBadges = { ProcessError: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextError', }, ProcessWarning: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextWarning', }, ProcessSuccess: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextSuccess', }, ProcessNeutral: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextNeutral', }, ProcessInProgress: { badgeVariant: 'default', - icon: ( - - ), + icon: , color: 'colorTextNeutral', }, ProcessDisabled: { badgeVariant: 'decorative10', - icon: , + icon: , color: 'colorTextWeak', }, ProcessDraft: { badgeVariant: 'decorative10', - icon: , + icon: , color: 'colorTextWeak', }, ConnectivityAvailable: { badgeVariant: 'default', - icon: ( - - ), + icon: , color: 'colorTextSuccess', }, ConnectivityBusy: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextWarning', }, ConnectivityUnavailable: { badgeVariant: 'default', - icon: ( - - ), + icon: , color: 'colorTextError', }, ConnectivityNeutral: { badgeVariant: 'default', - icon: , + icon: , color: 'colorTextNeutral', }, ConnectivityOffline: { badgeVariant: 'decorative10', - icon: , + icon: , color: 'colorTextWeak', }, } as const; diff --git a/packages/paste-core/components/status/stories/StatusMenu.stories.tsx b/packages/paste-core/components/status/stories/StatusMenu.stories.tsx index a0a1a1d64f..1dad106c10 100644 --- a/packages/paste-core/components/status/stories/StatusMenu.stories.tsx +++ b/packages/paste-core/components/status/stories/StatusMenu.stories.tsx @@ -40,7 +40,7 @@ const ProcessStatusMenu: React.FCError - In progress + In-progress Disabled @@ -201,7 +201,7 @@ const ProcessObject = { }, InProgress: { variant: 'ProcessInProgress', - children: 'In progress', + children: 'In-progress', }, Disabled: { variant: 'ProcessDisabled', @@ -315,7 +315,7 @@ export const Process: StoryFn = () => ( Warning Success Neutral - In Progress + In-progress Disabled Draft diff --git a/packages/paste-website/package.json b/packages/paste-website/package.json index 150111cf3c..084774ee39 100644 --- a/packages/paste-website/package.json +++ b/packages/paste-website/package.json @@ -27,6 +27,7 @@ "@next/bundle-analyzer": "^13.1.6", "@next/mdx": "^13.1.6", "@sparticuz/chromium": "^110.0.0", + "@twilio-paste/account-switcher": "^1.0.0", "@twilio-paste/alert": "^13.0.0", "@twilio-paste/alert-dialog": "^8.0.0", "@twilio-paste/anchor": "^11.0.0", @@ -89,6 +90,7 @@ "@twilio-paste/pagination": "^6.0.0", "@twilio-paste/paragraph": "^9.0.0", "@twilio-paste/popover": "^12.0.0", + "@twilio-paste/product-switcher": "1.0.0", "@twilio-paste/radio-button-group": "^3.0.0", "@twilio-paste/radio-group": "^12.0.0", "@twilio-paste/react-textarea-autosize-library": "^2.0.0", diff --git a/packages/paste-website/src/component-examples/AccountSwitcherExamples.ts b/packages/paste-website/src/component-examples/AccountSwitcherExamples.ts new file mode 100644 index 0000000000..3f668f8559 --- /dev/null +++ b/packages/paste-website/src/component-examples/AccountSwitcherExamples.ts @@ -0,0 +1,63 @@ +export const accountSwitcherExample = ` +const AccountSwitcherMenu = () => { + const accountSwitcher = useAccountSwitcherState(); + const [selectedAccount, setSelectedAccount] = React.useState('Owl Telehealth'); + return ( + <> + + Owl Telehealth + + + + setSelectedAccount('Owl Telehealth')} + {...accountSwitcher} + > + Owl Telehealth + + setSelectedAccount('Owl Health Demo')} + {...accountSwitcher} + > + Owl Health Demo + + setSelectedAccount('Owl Subway')} + {...accountSwitcher} + > + Owl Subway + + + + + Account settings + + + + View all accounts + + + View all subaccounts + + + + Admin Center + + + + ); +}; + +render( + +) +`.trim(); diff --git a/packages/paste-website/src/component-examples/DescriptionListExamples.ts b/packages/paste-website/src/component-examples/DescriptionListExamples.ts index 03e6e4e2f8..960fcc7bf7 100644 --- a/packages/paste-website/src/component-examples/DescriptionListExamples.ts +++ b/packages/paste-website/src/component-examples/DescriptionListExamples.ts @@ -53,8 +53,8 @@ const DescriptionListExample = () => { - In progress + title="In-progress" /> + In-progress diff --git a/packages/paste-website/src/component-examples/ProductSwitcherExamples.ts b/packages/paste-website/src/component-examples/ProductSwitcherExamples.ts new file mode 100644 index 0000000000..f8ffe507fa --- /dev/null +++ b/packages/paste-website/src/component-examples/ProductSwitcherExamples.ts @@ -0,0 +1,77 @@ +export const productSwitcherExample = ` +const ProductSwitcherMenu = () => { + const productSwitcher = useProductSwitcherState(); + const [product, setProduct] = React.useState('twilio'); + return ( + <> + + + { + setProduct('twilio'); + }} + productName="Twilio" + productStrapline="SMS, Voice & Video" + productIcon={} + /> + { + setProduct('segment'); + }} + productName="Segment" + productStrapline="Customer data platform" + productIcon={} + /> + { + setProduct('flex'); + }} + productName="Flex" + productStrapline="Cloud-based contact center" + productIcon={} + /> + { + setProduct('sendgrid'); + }} + productName="SendGrid" + productStrapline="Email delivery and API" + productIcon={} + /> + { + setProduct('admin'); + }} + productName="Console Admin" + productStrapline="Admin center" + productIcon={} + /> + + + ); +}; + +render( + +) +`.trim(); diff --git a/packages/paste-website/src/component-examples/StatusMenuExamples.ts b/packages/paste-website/src/component-examples/StatusMenuExamples.ts index 24ceabd4dd..2db293def4 100644 --- a/packages/paste-website/src/component-examples/StatusMenuExamples.ts +++ b/packages/paste-website/src/component-examples/StatusMenuExamples.ts @@ -17,7 +17,7 @@ export const ProcessObject = { }, InProgress: { variant: 'ProcessInProgress', - children: 'In progress', + children: 'In-progress', }, Disabled: { variant: 'ProcessDisabled', @@ -27,7 +27,7 @@ export const ProcessObject = { variant: 'ProcessDraft', children: 'Draft', }, -}; +} as const; export const processStatusMenuExample = ` const ProcessStatusMenu = () => { const [process, setProcess] = React.useState(ProcessObject.Success); @@ -42,28 +42,77 @@ const ProcessStatusMenu = () => { {process.children} - onClick('Success')} variant="default"> - Complete - - onClick('Neutral')} variant="default"> - In review - - onClick('Warning')} variant="default"> - Needs attention - - onClick('Error')} variant="default"> - Rejected - - onClick('InProgress')} variant="default"> - In progress - - onClick('Disabled')} variant="default"> - Paused - - onClick('Draft')} variant="default"> - Draft - - + onClick('Success')} + variant="default" + > + {ProcessObject.Success.children} + + onClick('Neutral')} + variant="default" + > + {ProcessObject.Neutral.children} + + onClick('Warning')} + variant="default" + > + {ProcessObject.Warning.children} + + onClick('Error')} + variant="default" + > + {ProcessObject.Error.children} + + onClick('InProgress')} + variant="default" + > + {ProcessObject.InProgress.children} + + onClick('Disabled')} + variant="default" + > + {ProcessObject.Disabled.children} + + onClick('Draft')} + variant="default" + > + {ProcessObject.Draft.children} + + ); }; diff --git a/packages/paste-website/src/component-examples/StatusPatternExamples.tsx b/packages/paste-website/src/component-examples/StatusPatternExamples.tsx index 4da8794bd5..0edb31a7f2 100644 --- a/packages/paste-website/src/component-examples/StatusPatternExamples.tsx +++ b/packages/paste-website/src/component-examples/StatusPatternExamples.tsx @@ -34,7 +34,7 @@ export const ProcessNeutral = (): JSX.Element => { }; export const ProcessInProgress = (): JSX.Element => { - return ; + return ; }; export const ProcessDisabled = (): JSX.Element => { @@ -103,8 +103,8 @@ export const processStatusExamples = ` - In Progress + title="In-progress" /> + In-progress {shouldShowStatusBadge && } {shouldShowFigmaNeeded && ( - - - Design assets pending - + + Design assets pending + )} {shouldShowPeerReviewNeeded ? ( - - - Peer review pending - + + Peer review pending + ) : null} ); diff --git a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx index 88837f7790..1de1f9fb2a 100644 --- a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx +++ b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx @@ -320,6 +320,30 @@ const SidebarNavigation = (): JSX.Element => { ); } + if (name === 'Status') { + return ( + + event({ + category: 'Left Navigation', + action: `click-${name}`, + label: name, + }) + } + > + + Status Badge + + + Status Menu + + + ); + } return ( {name} diff --git a/packages/paste-website/src/pages/components/account-switcher/index.mdx b/packages/paste-website/src/pages/components/account-switcher/index.mdx new file mode 100644 index 0000000000..7036ad1ba2 --- /dev/null +++ b/packages/paste-website/src/pages/components/account-switcher/index.mdx @@ -0,0 +1,349 @@ +export const meta = { + title: 'Account Switcher - Components', + package: '@twilio-paste/account-switcher', + description: "An Account Switcher is a stylized Menu Badge with a list of actions related to a user's accounts.", + slug: '/components/account-switcher/', +}; + +import Changelog from '@twilio-paste/account-switcher/CHANGELOG.md'; +import { + AccountSwitcher, + AccountSwitcherBadge, + AccountSwitcherGroup, + AccountSwitcherItem, + AccountSwitcherItemRadio, + AccountSwitcherSeparator, + useAccountSwitcherState, +} from '@twilio-paste/account-switcher'; + +import {Box} from '@twilio-paste/box'; + +import {SidebarCategoryRoutes} from '../../../constants'; +import packageJson from '@twilio-paste/account-switcher/package.json'; +import DefaultLayout from '../../../layouts/DefaultLayout'; +import {getFeature, getNavigationData} from '../../../utils/api'; +import {accountSwitcherExample} from '../../../component-examples/AccountSwitcherExamples'; + +export default DefaultLayout; + +export const getStaticProps = async () => { + const navigationData = await getNavigationData(); + const feature = await getFeature('Account Switcher'); + return { + props: { + data: { + ...packageJson, + ...feature, + }, + navigationData, + }, + }; +}; + + + +--- + + + + + + + + + {accountSwitcherExample} + + +## Guidelines + +## About Account Switcher + +Account Switcher can be used anywhere a user needs to switch between accounts or sub-accounts, and can usually be found in the Topbar. Other names for accounts in Twilio products include "workspaces" and "projects". + +Account Switcher is a stylized [Menu Badge](/components/menu#menu-badge), with groups of account options displayed in the menu item list. The Menu Badge itself should display the current account the user is viewing and update if a new account is selected. + +Examples of account-related options include: + +- Navigating to recently visited accounts +- Creating an account +- Viewing all of a user’s accounts + +Account Switcher is an implementation of the [Menu](/components/menu) component and shares a lot of its sub-components and hooks. That means it shares a lot of the API. + +### Accessibility + +The Account Switcher implements a grouped Menu and supports all the same keyboard navigation that the [Menu](/components/menu) component does. + +## Examples + + + {accountSwitcherExample} + + +--- + +## Composition Notes + +The Account Switcher is made up of the following sub-components: + +- `AccountSwitcherBadge` +- `AccountSwitcher` +- `AccountSwitcherGroup` +- `AccountSwitcherItem` +- `AccountSwitcherItemRadio` +- `AccountSwitcherSeparator` +- `useAccountSwitcherState` + +### Account Switcher Badge + +Used to display the name of the current account selected or being viewed, and contains the menu trigger. The text inside the badge should update when a new account is selected. + +### Account Switcher + +Container for Account Switcher menu items. + +### Account Switcher Group + +Used to group similar items together in the Account Switcher menu. An example of this might be a list of recent accounts. + +### Account Switcher Item + +A menu item that can either perform an action or navigate to a new URL. + +### Account Switcher ItemRadio + +A menu item that can perform a single selection of an item within a named group. Similar to a radio button group, only one item can be selected at a time. Each item in the group should have a `name` and `value` and must be contained in a Group. + +### Account Switcher Separator + +Simple horizontal rule used to separate groups or individual items. + +### useAccountSwitcherState hook + +React hook used to initialise the Account Switcher with various options. It returns state and actions that can be taken on the Account Switcher. + +--- + +## Usage Guide + +### API + +#### Installation + +```bash +yarn add @twilio-paste/account-switcher - or - yarn add @twilio-paste/account-switcher +``` + +#### Usage + +```jsx +import { + AccountSwitcher, + AccountSwitcherBadge, + AccountSwitcherGroup, + AccountSwitcherItem, + AccountSwitcherItemRadio, + AccountSwitcherSeparator, + useAccountSwitcherState, +} from '@twilio-paste/core/account-switcher'; + +const AccountSwitcherMenu = () => { + const accountSwitcher = useAccountSwitcherState(); + const [selectedAccount, setSelectedAccount] = React.useState('Owl Telehealth'); + return ( + <> + + Owl Telehealth + + + + setSelectedAccount('Owl Telehealth')} + {...accountSwitcher} + > + Owl Telehealth + + setSelectedAccount('Owl Health Demo')} + {...accountSwitcher} + > + Owl Health Demo + + setSelectedAccount('Owl Subway')} + {...accountSwitcher} + > + Owl Subway + + + + + Account settings + + + + View all accounts + + + View all subaccounts + + + + Admin Center + + + + ); +}; +``` + +#### Props + +##### useAccountSwitcherState + +Pass these as part of an object to the `useAccountSwitcherState` hook. + +| Prop | Type | Description | Default | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------- | +| baseId | `string` | ID that will serve as a base for all the items IDs. | | +| rtl | `boolean` | | | +| orientation | `horizontal, vertical, undefined` | | | +| currentId | `string, null, undefined` | The current focused item `id`. | | +| loop | `boolean, horizontal, vertical` | | | +| wrap | `boolean, horizontal, vertical` | | | +| visible | `boolean` | Whether it's visible or not. | | +| animated | `number, boolean` | | | +| placement | `auto-start, auto, auto-end, top-start, top, top-end, right-start, right, right-end, bottom-end, bottom, bottom-start, left-end, left, left-start` | | | +| gutter | `number, undefined` | Offset between the reference and the popover on the main axis. Should not be combined with `unstable_offset`. | | + +##### useAccountSwitcherState returned props + +These props are returned by the state hook. You can spread them into this component (`{...state}`) or pass them separately. You can also provide these props from your own state logic. + +| Prop | Type | Description | Default | +| ------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| baseId | `string` | ID that will serve as a base for all the items IDs. | | +| baseId | string | ID that will serve as a base for all the items IDs. | | +| visible | boolean | Whether it's visible or not. | | +| animated | number | boolean | | | +| modal | boolean | Toggles Dialog's `modal` state. | | +| animating | boolean | Whether it's animating or not. | | +| stopAnimation | () => void | Stops animation. It's called automatically if there's a CSS transition. | | +| hide | () => void | Changes the `visible` state to `false` | | +| placement | "auto-start" | "auto" | "auto-end" | "top-start... | Actual `placement`. | | +| orientation | "horizontal" | "vertical" | undefined | Defines the orientation of the composite widget. | | +| currentId | string | null | undefined | The current focused item `id`. | | +| wrap | boolean | "horizontal" | "vertical" | If enabled, moving to the next item from the last one in a row or column will focus the first item in the next row or column and vice-versa. | | +| groups | Group[] | Lists all the composite groups with their `id` and DOM `ref`. | | +| items | Item[] | Lists all the composite items with their `id`, DOM `ref`, `disabled` state and `groupId` if any. | | +| setCurrentId | (value: SetStateAction<string | null | undefine... | Sets `currentId`. | | +| first | () => void | Moves focus to the first item. | | +| last | () => void | Moves focus to the last item. | | +| move | (id: string | null) => void | Moves focus to a given item ID. | | +| next | (unstable_allTheWay?: boolean | undefined) => void | Moves focus to the next item. | | +| previous | (unstable_allTheWay?: boolean | undefined) => void | Moves focus to the previous item. | | + +##### AccountSwitcher + +| Prop | Type | Description | Default | +| ------------------ | -------------------- | ----------------------------------------------------------------------------------------- | ------------------ | +| hideOnClickOutside | `boolean, undefined` | When enabled, user can hide the dialog by clicking outside it. | | +| disabled | `boolean, undefined` | Same as the HTML attribute. | | +| element? | `string` | Overrides the default element name to apply unique styles with the Customization Provider | 'ACCOUNT_SWITCHER' | + +##### AccountSwitcherBadge + +| Prop | Type | Description | Default | +| --------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------ | +| as? | `string` | The HTML tag to replace the default `