UNPKG

@workday/canvas-kit-preview-react

Version:

Canvas Kit Preview is made up of components that have the full design and a11y review, are part of the DS ecosystem and are approved for use in product. The API's could be subject to change, but not without strong communication and migration strategies.

277 lines (276 loc) 11.3 kB
import React from 'react'; import { BaseButton, buttonStencil } from '@workday/canvas-kit-react/button'; import { createContainer, focusRing, styled } from '@workday/canvas-kit-react/common'; import { boxStyleFn, Flex } from '@workday/canvas-kit-react/layout'; import { borderRadius, colors, space, type } from '@workday/canvas-kit-react/tokens'; import { handleCsProp, px2rem } from '@workday/canvas-kit-styling'; import { usePillModel } from './usePillModel'; import { PillIcon } from './PillIcon'; import { PillIconButton } from './PillIconButton'; import { PillCount } from './PillCount'; import { PillAvatar } from './PillAvatar'; import { PillLabel } from './PillLabel'; import { systemIconStencil } from '@workday/canvas-kit-react/icon'; const StyledBasePill = styled(BaseButton)({ display: 'inline-flex', alignItems: 'center', borderRadius: borderRadius.m, flexShrink: 0, ...type.levels.subtext.large, color: colors.blackPepper400, boxShadow: 'none', outline: 'none', fontWeight: type.properties.fontWeights.medium, WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale', width: 'fit-content', padding: `2px ${space.xxs}`, height: space.m, position: 'relative', 'span[data-count="ck-pill-count"]': { borderTop: `${px2rem(1)} solid transparent`, borderBottom: `${px2rem(1)} solid transparent`, borderRight: `${px2rem(1)} solid transparent`, }, [buttonStencil.vars.background]: colors.soap300, [buttonStencil.vars.border]: colors.licorice200, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.color]: colors.licorice200, // This style ensures the removable button icon changes when you hover over the pill and not just the removable PillButton button: { [systemIconStencil.vars.color]: colors.licorice200, }, '&:focus-visible, &.focus': { [buttonStencil.vars.background]: colors.soap300, [buttonStencil.vars.border]: colors.blueberry400, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.color]: colors.licorice500, button: { [systemIconStencil.vars.color]: colors.licorice500, }, 'span[data-count="ck-pill-count"]': { borderColor: colors.blueberry400, }, }, '&:hover, &.hover': { [buttonStencil.vars.background]: colors.soap400, [buttonStencil.vars.border]: colors.licorice400, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.color]: colors.licorice500, button: { [systemIconStencil.vars.color]: colors.licorice500, }, }, '&:active, &.active': { [buttonStencil.vars.background]: colors.soap500, [buttonStencil.vars.border]: colors.licorice500, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.color]: colors.licorice500, button: { [buttonStencil.vars.background]: colors.soap500, [systemIconStencil.vars.color]: colors.licorice500, }, 'span[data-count="ck-pill-count"]': { backgroundColor: colors.soap600, borderColor: 'transparent', }, }, '&:disabled, &.disabled': { [buttonStencil.vars.background]: colors.soap100, [buttonStencil.vars.border]: colors.licorice100, [buttonStencil.vars.label]: colors.licorice100, [buttonStencil.vars.opacity]: '1', [systemIconStencil.vars.color]: colors.licorice100, button: { [systemIconStencil.vars.color]: colors.licorice100, }, }, }, ({ variant }) => ({ '&:focus-visible, &.focus': { borderColor: variant === 'removable' ? undefined : colors.blueberry400, ...focusRing({ width: 0, inset: 'inner', innerColor: colors.blueberry400, outerColor: colors.blueberry400, separation: 1, }), }, }), boxStyleFn); const StyledRemoveablePill = styled(StyledBasePill)({ [buttonStencil.vars.background]: colors.soap300, [buttonStencil.vars.border]: colors.licorice200, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.backgroundColor]: colors.soap100, '&:focus-visible, &.focus': { [buttonStencil.vars.background]: colors.soap300, [buttonStencil.vars.border]: colors.licorice200, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.backgroundColor]: colors.soap300, boxShadow: 'none', }, '&:hover, &.hover': { [buttonStencil.vars.background]: colors.soap300, [buttonStencil.vars.border]: colors.licorice200, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.backgroundColor]: colors.soap300, }, '&:active, &.active': { [buttonStencil.vars.background]: colors.soap500, [buttonStencil.vars.border]: colors.licorice500, [buttonStencil.vars.label]: colors.blackPepper400, [systemIconStencil.vars.backgroundColor]: colors.soap500, }, '&:disabled, &.disabled': { [buttonStencil.vars.background]: colors.soap100, [buttonStencil.vars.label]: colors.licorice100, [buttonStencil.vars.border]: colors.licorice100, [systemIconStencil.vars.backgroundColor]: colors.soap100, }, cursor: 'default', overflow: 'revert', position: 'relative', }); const StyledReadOnlyPill = styled(StyledRemoveablePill)({ [buttonStencil.vars.background]: 'transparent', '&:hover, &.hover': { [buttonStencil.vars.background]: 'transparent', }, '&:focus-visible, &.focus': { [buttonStencil.vars.background]: 'transparent', }, '&:active, &.active': { [buttonStencil.vars.background]: 'transparent', }, '&:disabled, &.disabled': { [buttonStencil.vars.background]: 'transparent', }, border: `${px2rem(1)} solid ${colors.licorice200}`, }); /** * By default, a `Pill` renders an interactive element that accepts subcomponents. By "interactive" * we mean that the Pill container is a focusable element (a `<button>`). All leading elements * (icons or avatars) are intended to be descriptive, helping support the label. They should not * receive focus. * * `Pill` is the container component. It also provides a React context model for its subcomponents. * Based on the `variant` prop this component will render different styled `Pill`s. * * Example of read only: * * ```tsx * <Pill variant="readOnly">This is a read only</Pill> * ``` * * Example of interactive: * * ```tsx * <Pill onClick={() => console.log('clicked')}> * <Pill.Avatar /> Regina Skeltor * </Pill> * ``` * * Example of removable: * * ```tsx * <Pill variant="removable"> * <Pill.Avatar /> Regina Skeltor * <Pill.IconButton onClick={() => console.log('clicked')} /> * </Pill> * ``` * * If you set the `Pill` `variant` to `removable`, it will render a `<span>` element. You can then * provide a `Pill.IconButton` that acts as the focus target. This creates a smaller, more * intentional click target that prevents users from accidentally deleting an item. * * ```tsx * <Pill variant="removable"> * Shoes * <Pill.IconButton onClick={() => console.log('handle remove')} /> * </Pill> * ``` */ export const Pill = createContainer('button')({ displayName: 'Pill', modelHook: usePillModel, subComponents: { /** * This component renders an avatar. It supports all props of the `Avatar` component. * * ```tsx * <Pill variant="removable"> * <Pill.Avatar url={avatarUrl} /> * Regina Skeltor * <Pill.IconButton onClick={() => console.log('handle remove')} /> * </Pill> * ``` */ Avatar: PillAvatar, /** * This component renders its `children` as the count. * * ```tsx * <Pill onClick={() => console.warn('clicked')}> * Shoes * <Pill.Count>30</Pill.Count> * </Pill> * ``` */ Count: PillCount, /** * This component renders an `icon`. It not be used with the `default` styling – not `readOnly` * or `removable` variants. By default it renders a `plusIcon` but it can be overridden by * providing an icon to the `icon` prop. * * ```tsx * <Pill onClick={() => console.warn('clicked')}> * <Pill.Icon /> * <Pill.Label>Regina Skeltor</Pill.Label> * </Pill> * ``` */ Icon: PillIcon, /** * This component renders a custom icon button. It is only intended to be used with the * `removable` variant. By default, it renders a `xSmallIcon` but can be overridden by providing * an icon to the `icon` prop. * * ```tsx * <Pill variant="removable"> * Pink Shirts * <Pill.IconButton onClick={() => console.warn('clicked')} /> * </Pill> * ``` */ IconButton: PillIconButton, /** * This component renders a `<span>` that automatically handles overflow by rendering a tooltip. * There's no need to use this component directly since the overflow is handled for you automatically. * * ```tsx * <Pill variant="readOnly"> * <Pill.Label>Read-only</Pill.Label> * </Pill> * ``` */ Label: PillLabel, }, })(({ variant = 'default', maxWidth, ...elemProps }, Element, model) => { return (React.createElement(React.Fragment, null, variant === 'readOnly' && (React.createElement(StyledReadOnlyPill, { as: Element !== 'button' ? Element : 'span', id: model.state.id, maxWidth: model.state.maxWidth, ...elemProps }, React.createElement(PillLabel, null, elemProps.children))), variant === 'default' && (React.createElement(StyledBasePill, { as: Element, ...elemProps, disabled: model.state.disabled }, React.createElement(Flex, { gap: "xxxs", display: "inline-flex", alignItems: "center" }, React.Children.map(elemProps.children, (child, index) => { if (typeof child === 'string') { return React.createElement(PillLabel, { key: index }, child); } return (React.createElement(Flex.Item, { key: index, display: "inline-flex" }, child)); })))), variant === 'removable' && (React.createElement(StyledRemoveablePill, { as: Element !== 'button' ? Element : 'span', variant: variant, type: undefined, ...handleCsProp(elemProps, [model.state.disabled ? 'disabled' : undefined]) }, React.createElement(Flex, { gap: "xxxs", display: "inline-flex", alignItems: "center", justifyContent: "center" }, React.Children.map(elemProps.children, (child, index) => { if (typeof child === 'string') { return React.createElement(PillLabel, { key: index }, child); } return React.createElement(Flex.Item, { key: index }, child); })))))); });