diff --git a/documentation-site/components/search.jsx b/documentation-site/components/search.jsx index 5eef4da3d1..953d47efa1 100644 --- a/documentation-site/components/search.jsx +++ b/documentation-site/components/search.jsx @@ -20,10 +20,10 @@ const PlainInput = themedStyled("input", ({ $theme }) => ({ borderRightWidth: "2px", borderTopWidth: "2px", borderBottomWidth: "2px", - borderLeftColor: $theme.colors.inputEnhancerFill, - borderRightColor: $theme.colors.inputEnhancerFill, - borderTopColor: $theme.colors.inputEnhancerFill, - borderBottomColor: $theme.colors.inputEnhancerFill, + borderLeftColor: $theme.colors.borderOpaque, + borderRightColor: $theme.colors.borderOpaque, + borderTopColor: $theme.colors.borderOpaque, + borderBottomColor: $theme.colors.borderOpaque, borderLeftStyle: "solid", borderRightStyle: "solid", borderTopStyle: "solid", @@ -51,7 +51,7 @@ const PlainInput = themedStyled("input", ({ $theme }) => ({ const SearchContainer = themedStyled("div", ({ $theme }) => ({ display: "flex", alignItems: "center", - backgroundColor: $theme.colors.inputEnhancerFill, + backgroundColor: $theme.colors.borderOpaque, position: "relative", })); diff --git a/documentation-site/components/yard/config/tag.ts b/documentation-site/components/yard/config/tag.ts index c87bfdf576..1c1f9fed0e 100644 --- a/documentation-site/components/yard/config/tag.ts +++ b/documentation-site/components/yard/config/tag.ts @@ -4,7 +4,7 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import { Tag, KIND, VARIANT, SIZE } from "baseui/tag"; +import { Tag, KIND, HIERARCHY, SIZE } from "baseui/tag"; import { PropTypes } from "react-view"; import type { TConfig } from "../types"; @@ -18,7 +18,7 @@ const TagConfig: TConfig = { scope: { Tag, KIND, - VARIANT, + HIERARCHY, SIZE, }, theme: [ @@ -113,16 +113,16 @@ const TagConfig: TConfig = { type: PropTypes.String, description: `The color theme to be applied to a Tag. To make this custom color active, you have to set kind to custom.`, }, - variant: { - value: "VARIANT.light", - defaultValue: "VARIANT.light", - options: VARIANT, + hierarchy: { + value: "HIERARCHY.secondary", + defaultValue: "HIERARCHY.secondary", + options: HIERARCHY, type: PropTypes.Enum, description: - "Defines tags look. Set it to one of VARIANT[key] values. Defaults to VARIANT.light.", + "Defines tags look. Set it to one of HIERARCHY[key] values. Defaults to HIERARCHY.secondary.", imports: { "baseui/tag": { - named: ["VARIANT"], + named: ["HIERARCHY"], }, }, }, @@ -183,7 +183,7 @@ const TagConfig: TConfig = { ], sharedProps: { $kind: "kind", - $variant: "variant", + $hierarchy: "hierarchy", $closeable: "closeable", $isActive: { type: PropTypes.Boolean, diff --git a/documentation-site/examples/tag/clickable-non-closeable.tsx b/documentation-site/examples/tag/clickable-non-closeable.tsx index 56261c42e9..a436300c42 100644 --- a/documentation-site/examples/tag/clickable-non-closeable.tsx +++ b/documentation-site/examples/tag/clickable-non-closeable.tsx @@ -1,20 +1,20 @@ import * as React from "react"; -import { Tag, VARIANT } from "baseui/tag"; +import { Tag, HIERARCHY } from "baseui/tag"; -const variants = Object.values(VARIANT); +const hierarchies = Object.values(HIERARCHY); const onClick = (kind: string) => alert(`${kind} tag is clicked`); export default function Example() { return ( - {variants.map((variant, index) => ( + {hierarchies.map((hierarchy, index) => ( { onClick("neutral"); }} - variant={variant} + hierarchy={hierarchy} kind="neutral" > neutral @@ -24,7 +24,7 @@ export default function Example() { onClick={() => { onClick("primary"); }} - variant={variant} + hierarchy={hierarchy} kind="primary" > primary @@ -34,7 +34,7 @@ export default function Example() { onClick={() => { onClick("accent"); }} - variant={variant} + hierarchy={hierarchy} kind="accent" > accent @@ -44,7 +44,7 @@ export default function Example() { onClick={() => { onClick("positive"); }} - variant={variant} + hierarchy={hierarchy} kind="positive" > positive @@ -54,7 +54,7 @@ export default function Example() { onClick={() => { onClick("warning"); }} - variant={variant} + hierarchy={hierarchy} kind="warning" > warning @@ -64,7 +64,7 @@ export default function Example() { onClick={() => { onClick("negative"); }} - variant={variant} + hierarchy={hierarchy} kind="negative" > negative diff --git a/documentation-site/examples/tag/clickable.tsx b/documentation-site/examples/tag/clickable.tsx index c4d7e82bc6..5a77c69ec2 100644 --- a/documentation-site/examples/tag/clickable.tsx +++ b/documentation-site/examples/tag/clickable.tsx @@ -1,19 +1,19 @@ import * as React from "react"; -import { Tag, VARIANT } from "baseui/tag"; +import { Tag, HIERARCHY } from "baseui/tag"; -const variants = Object.values(VARIANT); +const hierarchies = Object.values(HIERARCHY); const onClick = (kind: string) => alert(`${kind} tag is clicked`); export default function Example() { return ( - {variants.map((variant, index) => ( + {hierarchies.map((hierarchy, index) => ( { onClick("neutral"); }} - variant={variant} + hierarchy={hierarchy} kind="neutral" > neutral @@ -22,7 +22,7 @@ export default function Example() { onClick={() => { onClick("primary"); }} - variant={variant} + hierarchy={hierarchy} kind="primary" > primary @@ -31,7 +31,7 @@ export default function Example() { onClick={() => { onClick("accent"); }} - variant={variant} + hierarchy={hierarchy} kind="accent" > accent @@ -40,7 +40,7 @@ export default function Example() { onClick={() => { onClick("positive"); }} - variant={variant} + hierarchy={hierarchy} kind="positive" > positive @@ -49,7 +49,7 @@ export default function Example() { onClick={() => { onClick("warning"); }} - variant={variant} + hierarchy={hierarchy} kind="warning" > warning @@ -58,7 +58,7 @@ export default function Example() { onClick={() => { onClick("negative"); }} - variant={variant} + hierarchy={hierarchy} kind="negative" > negative diff --git a/documentation-site/examples/tag/custom-color.tsx b/documentation-site/examples/tag/custom-color.tsx index c6b5c2db40..4403f8e550 100644 --- a/documentation-site/examples/tag/custom-color.tsx +++ b/documentation-site/examples/tag/custom-color.tsx @@ -1,12 +1,12 @@ import * as React from "react"; -import { Tag, KIND, VARIANT } from "baseui/tag"; +import { Tag, KIND, HIERARCHY } from "baseui/tag"; export default function Example() { return ( {}} > @@ -19,7 +19,7 @@ export default function Example() {
{}} > diff --git a/documentation-site/examples/tag/disabled.tsx b/documentation-site/examples/tag/disabled.tsx index e579f487c1..2ca302ef8b 100644 --- a/documentation-site/examples/tag/disabled.tsx +++ b/documentation-site/examples/tag/disabled.tsx @@ -1,20 +1,20 @@ import * as React from "react"; -import { Tag, VARIANT } from "baseui/tag"; +import { Tag, HIERARCHY } from "baseui/tag"; -const variants = Object.values(VARIANT); +const hierarchies = Object.values(HIERARCHY); const onClick = (kind: string) => alert(`${kind} tag is clicked`); export default function Example() { return ( - {variants.map((variant, index) => ( + {hierarchies.map((hierarchy, index) => ( { onClick("neutral"); }} - variant={variant} + hierarchy={hierarchy} kind="neutral" > neutral @@ -24,7 +24,7 @@ export default function Example() { onClick={() => { onClick("primary"); }} - variant={variant} + hierarchy={hierarchy} kind="primary" > primary @@ -34,7 +34,7 @@ export default function Example() { onClick={() => { onClick("accent"); }} - variant={variant} + hierarchy={hierarchy} kind="accent" > accent @@ -44,7 +44,7 @@ export default function Example() { onClick={() => { onClick("positive"); }} - variant={variant} + hierarchy={hierarchy} kind="positive" > positive @@ -54,7 +54,7 @@ export default function Example() { onClick={() => { onClick("warning"); }} - variant={variant} + hierarchy={hierarchy} kind="warning" > warning @@ -64,7 +64,7 @@ export default function Example() { onClick={() => { onClick("negative"); }} - variant={variant} + hierarchy={hierarchy} kind="negative" > negative diff --git a/documentation-site/examples/tag/kinds.tsx b/documentation-site/examples/tag/kinds.tsx index f29086e3b2..889f263a3e 100644 --- a/documentation-site/examples/tag/kinds.tsx +++ b/documentation-site/examples/tag/kinds.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import {Tag, KIND, VARIANT} from 'baseui/tag'; +import * as React from "react"; +import { Tag, KIND, HIERARCHY } from "baseui/tag"; export default function Scenario() { return ( @@ -23,7 +23,7 @@ export default function Scenario() { kind={kind} onClick={() => alert(`click ${kind}`)} onActionClick={() => alert(`action ${kind}`)} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > {kind} diff --git a/documentation-site/examples/tag/non-clickable.tsx b/documentation-site/examples/tag/non-clickable.tsx index 83739e6090..442e94d66c 100644 --- a/documentation-site/examples/tag/non-clickable.tsx +++ b/documentation-site/examples/tag/non-clickable.tsx @@ -1,34 +1,34 @@ import * as React from "react"; -import { Tag, VARIANT } from "baseui/tag"; +import { Tag, HIERARCHY } from "baseui/tag"; -const variants = Object.values(VARIANT); +const hierarchies = Object.values(HIERARCHY); export default function Example() { return ( - {variants.map((variant, index) => ( + {hierarchies.map((hierarchy, index) => ( - + neutral - + primary - + accent - + positive - + warning - + negative
diff --git a/documentation-site/examples/tag/non-closeable.tsx b/documentation-site/examples/tag/non-closeable.tsx index 4b0e03bc0f..3a828b68d6 100644 --- a/documentation-site/examples/tag/non-closeable.tsx +++ b/documentation-site/examples/tag/non-closeable.tsx @@ -1,34 +1,34 @@ import * as React from "react"; -import { Tag, VARIANT } from "baseui/tag"; +import { Tag, HIERARCHY } from "baseui/tag"; -const variants = Object.values(VARIANT); +const hierarchies = Object.values(HIERARCHY); export default function Example() { return ( - {variants.map((variant, index) => ( + {hierarchies.map((hierarchy, index) => ( - + neutral - + primary - + accent - + positive - + warning - + negative
diff --git a/documentation-site/examples/tag/primitive.tsx b/documentation-site/examples/tag/primitive.tsx index 71f6976c35..5c59784a43 100644 --- a/documentation-site/examples/tag/primitive.tsx +++ b/documentation-site/examples/tag/primitive.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import {Tag, KIND, VARIANT} from 'baseui/tag'; +import * as React from "react"; +import { Tag, KIND, HIERARCHY } from "baseui/tag"; export default function Scenario() { return ( @@ -26,7 +26,7 @@ export default function Scenario() { kind={kind} onClick={() => alert(`click ${kind}`)} onActionClick={() => alert(`action ${kind}`)} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > {kind}
diff --git a/documentation-site/examples/tag/variants.tsx b/documentation-site/examples/tag/variants.tsx index 32a81a016c..f3b405fcbd 100644 --- a/documentation-site/examples/tag/variants.tsx +++ b/documentation-site/examples/tag/variants.tsx @@ -5,15 +5,11 @@ export default function Example() { return ( - solid + primary - light - - - - outlined + secondary ); diff --git a/src/accordion/accordion.tsx b/src/accordion/accordion.tsx index b01cb2c79d..57674bef2f 100644 --- a/src/accordion/accordion.tsx +++ b/src/accordion/accordion.tsx @@ -85,17 +85,17 @@ export default class Accordion extends React.Component item.current === document.activeElement); - if (activeItemIdx > 0) { + if (activeItemIdx >= 0) { + e.preventDefault(); const prevItem = itemRefs[activeItemIdx - 1]; prevItem.current && prevItem.current.focus(); } } if (e.keyCode === ARROW_DOWN) { - e.preventDefault(); const activeItemIdx = itemRefs.findIndex((item) => item.current === document.activeElement); - if (activeItemIdx < itemRefs.length - 1) { + if (activeItemIdx >= 0 && activeItemIdx < itemRefs.length - 1) { + e.preventDefault(); const nextItem = itemRefs[activeItemIdx + 1]; nextItem.current && nextItem.current.focus(); } diff --git a/src/accordion/styled-components.ts b/src/accordion/styled-components.ts index 90dfa443cc..198eb8cb83 100644 --- a/src/accordion/styled-components.ts +++ b/src/accordion/styled-components.ts @@ -72,7 +72,7 @@ export const ToggleIcon = styled<'svg', SharedStylePropsArg>('svg', (props) => { return { ...getSvgStyles(props), flexShrink: 0, - color: $color || $theme.colors.contentPrimary, + color: $color || $theme.colors.contentTierary, cursor: $disabled ? 'not-allowed' : 'pointer', }; }); diff --git a/src/accordion/types.ts b/src/accordion/types.ts index 52d45f16e6..d5fe816c32 100644 --- a/src/accordion/types.ts +++ b/src/accordion/types.ts @@ -111,7 +111,7 @@ export type StatelessAccordionProps = { type SharedPanelProps = { /** The content visible when Panel is expanded. */ - children: React.ReactNode; + children?: React.ReactNode; /** Defaults to the disabled value provided by the parent Accordion component. */ disabled?: boolean; /** Id for a panel, when provided populates aria-controls diff --git a/src/app-nav-bar/mobile-menu.tsx b/src/app-nav-bar/mobile-menu.tsx index 0541c97b3d..59b924d746 100644 --- a/src/app-nav-bar/mobile-menu.tsx +++ b/src/app-nav-bar/mobile-menu.tsx @@ -49,7 +49,7 @@ const MobileNavMenuItem = React.forwardRef((props, ref) => { // Replace with a user menu item renderer return ( - + ); } diff --git a/src/banner/types.ts b/src/banner/types.ts index ee9eb29486..22de2b9e7f 100644 --- a/src/banner/types.ts +++ b/src/banner/types.ts @@ -51,7 +51,7 @@ export type BannerProps = { // Visually convey the message text. artwork?: ArtworkContent; // Message to display. - children: React.ReactNode; + children?: React.ReactNode; // Determines message priority by rendering in pale or saturated colors. hierarchy?: Hierarchy; // Determines color scheme and conveys message intent. diff --git a/src/bottom-navigation/bottom-navigation.tsx b/src/bottom-navigation/bottom-navigation.tsx index df002be842..2dcac66235 100644 --- a/src/bottom-navigation/bottom-navigation.tsx +++ b/src/bottom-navigation/bottom-navigation.tsx @@ -118,7 +118,7 @@ const BottomNavigation = ({ return ( } - endEnhancer={() => } + endEnhancer={() => } onClick={() => { activeKey === idx && !displayOverflow ? scrollToTop(idx) diff --git a/src/bottom-navigation/selector.tsx b/src/bottom-navigation/selector.tsx index bdaac3a0b0..75461621af 100644 --- a/src/bottom-navigation/selector.tsx +++ b/src/bottom-navigation/selector.tsx @@ -21,7 +21,9 @@ export const Selector = ({ title, icon, isActive, onChange, overrides = {} }: Se {title} diff --git a/src/bottom-navigation/styled-components.ts b/src/bottom-navigation/styled-components.ts index a0c540ae4f..220399d655 100644 --- a/src/bottom-navigation/styled-components.ts +++ b/src/bottom-navigation/styled-components.ts @@ -52,7 +52,9 @@ export const StyledTitle = styled<'div', { $isActive: boolean }>( 'div', ({ $theme, $isActive }) => ({ ...$theme.typography.LabelXSmall, - color: $isActive ? $theme.colors.contentPrimary : $theme.colors.contentTertiary, + color: $isActive + ? $theme.colors.bottomNavigationSelectedText + : $theme.colors.bottomNavigationText, }) ); StyledTitle.displayName = 'StyledTitle'; diff --git a/src/button-docked/button-docked.tsx b/src/button-docked/button-docked.tsx deleted file mode 100644 index 1abb8adcc3..0000000000 --- a/src/button-docked/button-docked.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -import * as React from 'react'; -import type { ButtonDockedProps } from './types'; -import { StyledRoot, StyledActionContainer, StyledActionSubContainer } from './styled-components'; -import { getOverrides } from '../helpers/overrides'; - -const ButtonDocked = (props: ButtonDockedProps) => { - const { primaryAction, secondaryActions, dismissiveAction, topAccessory, overrides = {} } = props; - - const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); - const [ActionContainer, actionContainerProps] = getOverrides( - overrides.ActionContainer, - StyledActionContainer - ); - const [ActionSubContainer, actionSubContainerProps] = getOverrides( - overrides.ActionSubContainer, - StyledActionSubContainer - ); - - return ( - <Root {...rootProps}> - {topAccessory} - <ActionContainer {...actionContainerProps}> - <ActionSubContainer {...actionSubContainerProps}>{secondaryActions}</ActionSubContainer> - <ActionSubContainer $reverseWhenWide {...actionSubContainerProps}> - {primaryAction} - {dismissiveAction} - </ActionSubContainer> - </ActionContainer> - </Root> - ); -}; - -export default ButtonDocked; diff --git a/src/button-docked/index.ts b/src/button-docked/index.ts deleted file mode 100644 index 5fd8fc0afb..0000000000 --- a/src/button-docked/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -export { default as ButtonDocked } from './button-docked'; -export * from './styled-components'; -export * from './types'; diff --git a/src/button-docked/styled-components.ts b/src/button-docked/styled-components.ts deleted file mode 100644 index be62ddf54f..0000000000 --- a/src/button-docked/styled-components.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -import { styled } from '../styles/index'; - -export const StyledRoot = styled<'div', {}>('div', ({ $theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: $theme.sizing.scale300, - paddingTop: $theme.sizing.scale600, - paddingRight: $theme.sizing.scale600, - paddingBottom: $theme.sizing.scale600, - paddingLeft: $theme.sizing.scale600, - backgroundColor: $theme.colors.backgroundPrimary, - container: 'root / inline-size', -})); -StyledRoot.displayName = 'StyledRoot'; - -export const StyledActionContainer = styled<'div', {}>('div', ({ $theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: $theme.sizing.scale300, - '@container root (min-width: 600px)': { - flexDirection: 'row', - justifyContent: 'space-between', - }, -})); -StyledActionContainer.displayName = 'StyledActionContainer'; - -export const StyledActionSubContainer = styled<'div', { $reverseWhenWide }>( - 'div', - ({ $reverseWhenWide, $theme }) => ({ - display: 'flex', - flexDirection: 'column', - gap: $theme.sizing.scale300, - '@container root (min-width: 600px)': { - flexDirection: $reverseWhenWide ? 'row-reverse' : 'row', - }, - }) -); -StyledActionSubContainer.displayName = 'StyledActionSubContainer'; diff --git a/src/button-docked/types.ts b/src/button-docked/types.ts deleted file mode 100644 index 6fd97828f9..0000000000 --- a/src/button-docked/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -import type { Override } from '../helpers/overrides'; - -export type ButtonDockedOverrides = { - Root?: Override; - ActionContainer?: Override; - ActionSubContainer?: Override; -}; - -export type ButtonDockedProps = { - primaryAction: React.ReactNode; - secondaryActions?: [React.ReactNode] | [React.ReactNode, React.ReactNode]; - dismissiveAction?: React.ReactNode; - topAccessory?: React.ReactNode; - overrides?: ButtonDockedOverrides; -}; diff --git a/src/button-timed/button-timed.tsx b/src/button-timed/button-timed.tsx index 774066c908..aa85a0b625 100644 --- a/src/button-timed/button-timed.tsx +++ b/src/button-timed/button-timed.tsx @@ -17,6 +17,7 @@ const ButtonTimed = (props: ButtonTimedProps) => { paused = false, onClick: onClickProp, disabled, + kind = KIND.primary, children, overrides = {}, ...restProps @@ -61,7 +62,7 @@ const ButtonTimed = (props: ButtonTimedProps) => { const buttonMergedOverrides = mergeOverrides( { - BaseButton: { + Root: { component: StyledBaseButtonTimed, props: { $initialTime: initialTime, @@ -75,9 +76,7 @@ const ButtonTimed = (props: ButtonTimedProps) => { }, }, { - Root: buttonOverrides.Root || {}, - // @ts-ignore - BaseButton: buttonOverrides.BaseButton, + Root: buttonOverrides.Root || buttonOverrides.BaseButton || {}, StartEnhancer: buttonOverrides.StartEnhancer || {}, EndEnhancer: buttonOverrides.EndEnhancer || {}, LoadingSpinnerContainer: buttonOverrides.LoadingSpinnerContainer || {}, @@ -91,7 +90,7 @@ const ButtonTimed = (props: ButtonTimedProps) => { overrides={buttonMergedOverrides} onClick={onClick} size={SIZE.large} - kind={KIND.primary} + kind={kind} shape={SHAPE.default} disabled={disabled || timeRemaining === 0} > diff --git a/src/button-timed/types.ts b/src/button-timed/types.ts index e509d4a03d..38a0ad1d7e 100644 --- a/src/button-timed/types.ts +++ b/src/button-timed/types.ts @@ -11,10 +11,7 @@ export type ButtonTimedOverrides = ButtonOverrides & { TimerContainer?: Override; }; -export type ButtonTimedProps = Omit< - ButtonProps, - 'kind' | 'shape' | 'size' | 'onClick' | 'overrides' -> & { +export type ButtonTimedProps = Omit<ButtonProps, 'shape' | 'size' | 'onClick' | 'overrides'> & { initialTime: number; // in seconds paused?: boolean; onClick: (a?: React.SyntheticEvent<HTMLButtonElement>) => unknown; diff --git a/src/button/__tests__/button.test.tsx b/src/button/__tests__/button.test.tsx index a9eae5e380..8af161cc90 100644 --- a/src/button/__tests__/button.test.tsx +++ b/src/button/__tests__/button.test.tsx @@ -7,6 +7,7 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; import { render, fireEvent, getByText } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import { Button } from '..'; @@ -97,3 +98,72 @@ describe('Button Component', () => { ).not.toThrow(); }); }); + +describe('Link Button', () => { + test('renders as an anchor tag with href', () => { + const { container } = render(<Button href="https://example.com">content</Button>); + const anchor = container.querySelector('a'); + expect(anchor).not.toBeNull(); + expect(anchor).toHaveAttribute('href', 'https://example.com'); + }); + + test('click event on anchor tag', () => { + const onClick = jest.fn(); + const { container } = render( + <Button href="https://example.com" onClick={onClick}> + content + </Button> + ); + const anchor = container.querySelector('a'); + if (anchor) fireEvent.click(anchor); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + test('renders as an anchor tag with href and target', () => { + const { container } = render( + <Button href="https://example.com" target="_blank"> + content + </Button> + ); + const anchor = container.querySelector('a'); + expect(anchor).not.toBeNull(); + expect(anchor).toHaveAttribute('href', 'https://example.com'); + expect(anchor).toHaveAttribute('target', '_blank'); + }); + + test('renders with href, target and other props', () => { + const { container } = render( + <Button href="https://example.com" target="_blank" startEnhancer="start" endEnhancer="end"> + content + </Button> + ); + const anchor = container.querySelector('a'); + expect(anchor).toHaveAttribute('href', 'https://example.com'); + expect(anchor).toHaveAttribute('target', '_blank'); + getByText(container, 'start'); + getByText(container, 'end'); + }); + + test('onClick overrides default href behavior', () => { + const onClick = jest.fn((event) => event.preventDefault()); + const { container } = render( + <Button href="https://example.com" onClick={onClick}> + content + </Button> + ); + const anchor = container.querySelector('a'); + if (anchor) fireEvent.click(anchor); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + test('renders only an anchor tag', () => { + const { container } = render(<Button href="https://example.com">content</Button>); + const allElements = container.firstChild?.childNodes; + expect(allElements).toHaveLength(1); + const anchor = container.querySelector('a'); + expect(anchor).not.toBeNull(); + expect(anchor?.childNodes).toHaveLength(1); + expect(anchor?.firstChild?.nodeType).toBe(Node.TEXT_NODE); + expect(anchor?.textContent).toBe('content'); + }); +}); diff --git a/src/button/button.tsx b/src/button/button.tsx index b0376580c9..a6e5e681f0 100644 --- a/src/button/button.tsx +++ b/src/button/button.tsx @@ -7,6 +7,7 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; import { BaseButton as StyledBaseButton, + AnchorBaseButton as StyledAnchorBaseButton, LoadingSpinner as StyledLoadingSpinner, LoadingSpinnerContainer as StyledLoadingSpinnerContainer, } from './styled-components'; @@ -80,12 +81,14 @@ class Button extends React.Component< ...restProps } = this.props; // Get overrides + const isAnchor = 'href' in restProps && Boolean(restProps?.href); + const [BaseButton, baseButtonProps] = getOverrides( // adding both (1) BaseButton and (2) Root // (1) because it's a Button under the hood // (2) because we want consistency with the rest of the components overrides.BaseButton || overrides.Root, - StyledBaseButton + isAnchor ? StyledAnchorBaseButton : StyledBaseButton ); const [LoadingSpinner, loadingSpinnerProps] = getOverrides<SharedStyleProps>( overrides.LoadingSpinner, @@ -110,11 +113,14 @@ class Button extends React.Component< } : {}; + const ariaDisabledProps = restProps?.disabled && isAnchor ? { ['aria-disabled']: true } : {}; + return ( <BaseButton ref={forwardedRef} data-baseweb="button" {...ariaLoadingElements} + {...ariaDisabledProps} {...sharedProps} {...restProps} {...baseButtonProps} @@ -143,10 +149,8 @@ class Button extends React.Component< export interface ButtonComponentType { <C extends React.ElementType = 'button'>( props: ButtonProps & - SharedStyleProps & - Omit<React.ComponentProps<C>, keyof ButtonProps | keyof SharedStyleProps> & { - $as?: C | React.ComponentType<any> | keyof JSX.IntrinsicElements; - } + Omit<React.ComponentProps<C>, keyof ButtonProps | keyof SharedStyleProps> & + SharedStyleProps<C | React.ComponentType<any> | keyof JSX.IntrinsicElements> ): JSX.Element; displayName?: string; } diff --git a/src/button/styled-components.ts b/src/button/styled-components.ts index f558d19af4..6323a46fd9 100644 --- a/src/button/styled-components.ts +++ b/src/button/styled-components.ts @@ -4,68 +4,95 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import { styled } from '../styles'; +import type { StyleObject } from 'styletron-standard'; + +import { styled, type Theme } from '../styles'; import { KIND, SIZE, SHAPE } from './constants'; import type { SharedStyleProps } from './types'; import type { Font } from '../themes/types'; -export const BaseButton = styled<'button', SharedStyleProps>( - 'button', - ({ - $theme, - $size, - $colors, - $kind, - $shape, - $isLoading, - $isSelected, - $disabled, - $isFocusVisible, - }) => ({ - display: 'inline-flex', - // need to maintain button width while showing loading spinner - flexDirection: $isLoading ? 'column' : 'row', - alignItems: 'center', - justifyContent: 'center', - borderLeftWidth: 0, - borderTopWidth: 0, - borderRightWidth: 0, - borderBottomWidth: 0, - borderLeftStyle: 'none', - borderTopStyle: 'none', - borderRightStyle: 'none', - borderBottomStyle: 'none', - outline: 'none', - boxShadow: $isFocusVisible ? `inset 0 0 0 3px ${$theme.colors.borderAccent}` : 'none', - textDecoration: 'none', - WebkitAppearance: 'none', - transitionProperty: 'background', - transitionDuration: $theme.animation.timing200, - transitionTimingFunction: $theme.animation.linearCurve, - cursor: 'pointer', - ':disabled': { - cursor: 'not-allowed', - ...getDisabledStyles({ $theme, $kind, $disabled, $isSelected }), - }, - marginLeft: 0, - marginTop: 0, - marginRight: 0, - marginBottom: 0, - ...getFontStyles({ $theme, $size }), - ...getBorderRadiiStyles({ $theme, $size, $shape }), - ...getPaddingStyles({ $theme, $size, $shape }), - ...getColorStyles({ +// note: Tried doing a standard override of the styled function, but it didn't work +// it seems like there is some bug when override $as +type InternalStyleFn = (props: { $theme: Theme } & SharedStyleProps) => StyleObject; +const createStyledBaseButton = <T extends 'button' | 'a'>(type: T, styleFn?: InternalStyleFn) => + styled<T, SharedStyleProps>( + type, + ({ $theme, + $size, $colors, $kind, + $shape, $isLoading, $isSelected, $disabled, - }), - ...getShapeStyles({ $shape, $size }), - }) -); + $isFocusVisible, + $as, + }) => ({ + display: 'inline-flex', + // need to maintain button width while showing loading spinner + flexDirection: $isLoading ? 'column' : 'row', + alignItems: 'center', + justifyContent: 'center', + borderLeftWidth: 0, + borderTopWidth: 0, + borderRightWidth: 0, + borderBottomWidth: 0, + borderLeftStyle: 'none', + borderTopStyle: 'none', + borderRightStyle: 'none', + borderBottomStyle: 'none', + outline: 'none', + boxShadow: $isFocusVisible ? `inset 0 0 0 3px ${$theme.colors.borderAccent}` : 'none', + textDecoration: 'none', + WebkitAppearance: 'none', + transitionProperty: 'background', + transitionDuration: $theme.animation.timing200, + transitionTimingFunction: $theme.animation.linearCurve, + cursor: 'pointer', + ':disabled': { + cursor: 'not-allowed', + ...getDisabledStyles({ $theme, $kind, $disabled, $isSelected }), + }, + marginLeft: 0, + marginTop: 0, + marginRight: 0, + marginBottom: 0, + ...getFontStyles({ $theme, $size }), + ...getBorderRadiiStyles({ $theme, $size, $shape }), + ...getPaddingStyles({ $theme, $size, $shape }), + ...getColorStyles({ + $theme, + $colors, + $kind, + $isLoading, + $isSelected, + $disabled, + }), + ...getAnchorDisabledStyles({ $as, $theme, $kind, $isSelected, $disabled }), + ...getShapeStyles({ $shape, $size }), + ...styleFn?.({ + $theme, + $size, + $colors, + $kind, + $shape, + $isLoading, + $isSelected, + $disabled, + $isFocusVisible, + $as, + }), + }) + ); +export const BaseButton = createStyledBaseButton('button'); +BaseButton.displayName = 'BaseButton'; +export const AnchorBaseButton = createStyledBaseButton( + 'a', + ({ $theme, $kind, $isSelected, $disabled }) => + getAnchorDisabledStyles({ $as: 'a', $theme, $kind, $isSelected, $disabled }) +); BaseButton.displayName = 'BaseButton'; export const EndEnhancer = styled<'div', SharedStyleProps>('div', ({ $theme }) => { @@ -237,6 +264,18 @@ function getFontStyles({ $theme, $size }): Font { } } +function getAnchorDisabledStyles({ $as, $theme, $kind, $isSelected, $disabled }) { + if (!($as === 'a' && $disabled)) { + return {}; + } + + return { + cursor: 'not-allowed', + pointerEvents: 'none', + ...getDisabledStyles({ $theme, $kind, $isSelected, $disabled }), + }; +} + // @ts-ignore function getDisabledStyles({ $theme, $kind, $isSelected, $disabled }) { if ($disabled && $isSelected) { diff --git a/src/button/types.ts b/src/button/types.ts index 0e7664255d..64e2ddf6dc 100644 --- a/src/button/types.ts +++ b/src/button/types.ts @@ -9,6 +9,8 @@ import type * as React from 'react'; import type { KIND, SIZE, SHAPE } from './constants'; import type { Override } from '../helpers/overrides'; +type AnchorProps = React.HTMLProps<HTMLAnchorElement>; + export type ButtonOverrides = { Root?: Override; BaseButton?: Override; @@ -23,7 +25,7 @@ export type CustomColors = { color: string; }; -export type ButtonProps = { +interface BaseButtonProps { children?: React.ReactNode; colors?: CustomColors; disabled?: boolean; @@ -46,9 +48,21 @@ export type ButtonProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any startEnhancer?: React.ReactNode | React.ComponentType<any>; type?: 'submit' | 'reset' | 'button'; -}; +} + +export interface LinkButtonProps { + /** Convert button to <a> tag allowing for opening links directly. + * + * Use this over window.open as it handles accessibility better. + */ + href?: string | null; + /* Controls target of href */ + target?: string; +} + +export interface ButtonProps extends BaseButtonProps, LinkButtonProps {} -export type SharedStyleProps = { +export type SharedStyleProps<AS = React.ComponentType<any> | keyof JSX.IntrinsicElements> = { $colors?: CustomColors; $kind?: keyof typeof KIND; $isSelected?: boolean; @@ -57,4 +71,5 @@ export type SharedStyleProps = { $isLoading?: boolean; $disabled?: boolean; $isFocusVisible?: boolean; + $as?: AS; }; diff --git a/src/data-table/__tests__/column-categorical.test.tsx b/src/data-table/__tests__/column-categorical.test.tsx index cba7a13ebb..6dcec0fa6d 100644 --- a/src/data-table/__tests__/column-categorical.test.tsx +++ b/src/data-table/__tests__/column-categorical.test.tsx @@ -183,7 +183,7 @@ describe('categorical column', () => { expect(checkboxes.length).toBe(2); }); - it('quick actions hide when search query present', () => { + it('selects all filtered options when search query is present', () => { const column = CategoricalColumn({ title: 'column', mapDataToValue: () => '', @@ -192,18 +192,19 @@ describe('categorical column', () => { const mockSetFilter = jest.fn(); const data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']; - const { container, queryByText } = render( + const { container, getByText } = render( <Filter setFilter={mockSetFilter} close={() => {}} data={data} /> ); - expect(queryByText('Select All')).toBeTruthy(); - expect(queryByText('Clear')).toBeTruthy(); - const input = container.querySelector('[data-baseweb="input"] input'); if (input) fireEvent.change(input, { target: { value: 'a' } }); - expect(queryByText('Select All')).toBeFalsy(); - expect(queryByText('Clear')).toBeFalsy(); + fireEvent.click(getByText('Select All')); + + const checkboxes = container.querySelectorAll('input[type="checkbox"]'); + expect(checkboxes.length).toBe(2); // filtered option + exclude toggle + + expect((checkboxes[0] as unknown as HTMLInputElement).checked).toBe(true); }); it('builds expected filter function', () => { diff --git a/src/data-table/__tests__/data-table.test.tsx b/src/data-table/__tests__/data-table.test.tsx index fe49f15a2e..77ef723904 100644 --- a/src/data-table/__tests__/data-table.test.tsx +++ b/src/data-table/__tests__/data-table.test.tsx @@ -125,4 +125,24 @@ describe('Data Table', () => { expect(Array.isArray(rows) && rows[0].data).toEqual([true, 'blue', 1, 'brittle']); expect(Array.isArray(rows) && rows[1].data).toEqual([true, 'green', 2, 'bright']); }); + + it('renders and filters by initialTextQuery', async () => { + const controlRef = React.createRef() as ControlRef; + const { container } = render( + <TestBaseProvider> + <div style={{ height: '800px', width: '900px' }}> + <StatefulDataTable + columns={COLUMNS} + rows={ROWS} + controlRef={controlRef} + initialTextQuery="orange" + /> + </div> + </TestBaseProvider> + ); + + // should find only the two rows with orange from the mock ROWS + const rows = controlRef.current?.getRows(); + expect(Array.isArray(rows) && rows.length).toBe(2); + }); }); diff --git a/src/data-table/column-categorical.tsx b/src/data-table/column-categorical.tsx index 9a667ba46a..9228188ed9 100644 --- a/src/data-table/column-categorical.tsx +++ b/src/data-table/column-categorical.tsx @@ -155,24 +155,22 @@ export function CategoricalFilter(props: CategoricalFilterProps) { /> )} - {!query && ( - <div - style={{ - // @ts-ignore - marginTop: showQuery ? theme.sizing.scale600 : null, + <div + style={{ + // @ts-ignore + marginTop: showQuery ? theme.sizing.scale600 : null, + }} + > + <FilterQuickControls + onSelectAll={() => { + filteredCategories.forEach((c) => selection.add(c)); + setSelection(new Set(selection)); }} - > - <FilterQuickControls - onSelectAll={() => { - categories.forEach((c) => selection.add(c)); - setSelection(new Set(selection)); - }} - onClearSelection={() => { - setSelection(new Set()); - }} - /> - </div> - )} + onClearSelection={() => { + setSelection(new Set()); + }} + /> + </div> <div className={css({ diff --git a/src/data-table/stateful-container.ts b/src/data-table/stateful-container.ts index 351fa6a063..9064ab5c61 100644 --- a/src/data-table/stateful-container.ts +++ b/src/data-table/stateful-container.ts @@ -29,7 +29,9 @@ export const StatefulContainer: React.FC<StatefulContainerProps> = (props) => { ); const [sortDirection, setSortDirection] = React.useState(props.initialSortDirection); const [filters, setFilters] = React.useState(props.initialFilters || new Map()); - const [textQuery, setTextQuery] = React.useState(''); + const [textQuery, setTextQuery] = React.useState( + typeof props.initialTextQuery === 'string' ? props.initialTextQuery : '' + ); const [selectedRowIds, setSelectedRowIds] = React.useState<Set<string | number>>( props.initialSelectedRowIds || new Set() ); diff --git a/src/data-table/stateful-data-table.tsx b/src/data-table/stateful-data-table.tsx index 327d08f610..c1c8f64503 100644 --- a/src/data-table/stateful-data-table.tsx +++ b/src/data-table/stateful-data-table.tsx @@ -45,7 +45,7 @@ function useResizeObserver( function QueryInput(props) { const [css, theme] = useStyletron(); const locale = React.useContext(LocaleContext); - const [value, setValue] = React.useState(''); + const [value, setValue] = React.useState(props.textQuery); React.useEffect(() => { const timeout = setTimeout(() => props.onChange(value), 250); @@ -167,6 +167,7 @@ export function StatefulDataTable(props: StatefulDataTableProps) { initialSelectedRowIds={props.initialSelectedRowIds} initialSortIndex={props.initialSortIndex} initialSortDirection={props.initialSortDirection} + initialTextQuery={props.initialTextQuery} onFilterAdd={props.onFilterAdd} onFilterRemove={props.onFilterRemove} onIncludedRowsChange={props.onIncludedRowsChange} @@ -209,7 +210,7 @@ export function StatefulDataTable(props: StatefulDataTableProps) { paddingTop: theme.sizing.scale500, })} > - {searchable && <QueryInput onChange={onTextQueryChange} />} + {searchable && <QueryInput onChange={onTextQueryChange} textQuery={textQuery} />} {filterable && ( <React.Fragment> diff --git a/src/data-table/types.ts b/src/data-table/types.ts index c133b74525..16ebfcc151 100644 --- a/src/data-table/types.ts +++ b/src/data-table/types.ts @@ -111,6 +111,7 @@ export type StatefulDataTableProps = { initialSelectedRowIds?: Set<number | string>; initialSortIndex?: number; initialSortDirection?: SortDirections; + initialTextQuery?: string; loading?: boolean; loadingMessage?: string | React.ComponentType<{}>; onFilterAdd?: ( diff --git a/src/datepicker/day.tsx b/src/datepicker/day.tsx index eef6faa7a9..741c3a8408 100644 --- a/src/datepicker/day.tsx +++ b/src/datepicker/day.tsx @@ -241,8 +241,9 @@ export default class Day<T = Date> extends React.Component<DayProps<T>, DayState } clampToDayStart: (a: T) => T = (dt) => { - const { setSeconds, setMinutes, setHours } = this.dateHelpers; - return setSeconds(setMinutes(setHours(dt, 0), 0), 0); + const { startOfDay } = this.dateHelpers; + + return startOfDay(dt); }; // calculated for range case only diff --git a/src/datepicker/utils/__tests__/date-helpers.node.ts b/src/datepicker/utils/__tests__/date-helpers.node.ts new file mode 100644 index 0000000000..e8af054f57 --- /dev/null +++ b/src/datepicker/utils/__tests__/date-helpers.node.ts @@ -0,0 +1,46 @@ +import DateHelpers from '../date-helpers'; +import dateFnsAdapter from '../date-fns-adapter'; + +describe('date-helpers', () => { + const dateHelpers = new DateHelpers(dateFnsAdapter); + let mockDate: Date; + + it('setSeconds', () => { + const { setSeconds } = dateHelpers; + + mockDate = new Date(2028, 3, 14, 18, 33, 18, 938); + const mockNewSeconds = 12; + expect(setSeconds(mockDate, mockNewSeconds).toISOString()).toEqual( + new Date(2028, 3, 14, 18, 33, mockNewSeconds, 938).toISOString() + ); + }); + + it('setMinutes', () => { + const { setMinutes } = dateHelpers; + + mockDate = new Date(2028, 3, 14, 18, 33, 18, 938); + const mockNewMinutes = 59; + expect(setMinutes(mockDate, mockNewMinutes).toISOString()).toEqual( + new Date(2028, 3, 14, 18, mockNewMinutes, 18, 938).toISOString() + ); + }); + + it('setHours', () => { + const { setHours } = dateHelpers; + + mockDate = new Date(2028, 3, 14, 18, 33, 18, 938); + const mockNewHours = 3; + expect(setHours(mockDate, mockNewHours).toISOString()).toEqual( + new Date(2028, 3, 14, mockNewHours, 33, 18, 938).toISOString() + ); + }); + + it('startOfDay', () => { + const { startOfDay } = dateHelpers; + + mockDate = new Date(2028, 3, 14, 18, 33, 18, 938); + expect(startOfDay(mockDate).toISOString()).toEqual( + new Date(2028, 3, 14, 0, 0, 0, 0).toISOString() + ); + }); +}); diff --git a/src/datepicker/utils/date-helpers.ts b/src/datepicker/utils/date-helpers.ts index 5dd9fce64a..2fe592a15c 100644 --- a/src/datepicker/utils/date-helpers.ts +++ b/src/datepicker/utils/date-helpers.ts @@ -426,6 +426,9 @@ class DateHelpers<T> { setMonth: (b: T, a: number) => T = (date, monthNumber) => this.adapter.setMonth(date, monthNumber); setYear: (b: T, a: number) => T = (date, yearNumber) => this.adapter.setYear(date, yearNumber); + startOfDay: (a: T) => T = (date) => { + return this.adapter.startOfDay(date); + }; getMinutes: (a: T) => number = (date) => this.adapter.getMinutes(date); getHours: (a: T) => number = (date) => this.adapter.getHours(date); getMonth: (a: T) => number = (date) => this.adapter.getMonth(date); diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index 4766043e2a..f9dc144f8d 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -4,15 +4,23 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import React, { isValidElement } from 'react'; +import React, { useEffect, useRef, isValidElement } from 'react'; import type { ReactNode, ComponentType } from 'react'; +import FocusLock from 'react-focus-lock'; +import { Layer } from '../layer'; import { useStyletron } from '../styles/index'; import { getOverrides } from '../helpers/overrides'; import { ButtonDock } from '../button-dock'; import { Button, KIND, SHAPE } from '../button'; import { Delete } from '../icon'; -import { StyledHeading, StyledBody, StyledRoot, StyledScrollContainer } from './styled-components'; +import { + StyledHeading, + StyledBody, + StyledRoot, + StyledScrollContainer, + StyledOverlay, +} from './styled-components'; import { PLACEMENT, SIZE } from './constants'; import type { DialogProps, Artwork } from './types'; @@ -28,7 +36,9 @@ function renderArtwork(artwork?: Artwork): ReactNode { const DefaultDismissButton = (props) => { const overrides = { + ...props.overrides, BaseButton: { + ...props.overrides?.BaseButton, style: { position: 'absolute', top: '16px', @@ -36,11 +46,12 @@ const DefaultDismissButton = (props) => { // this will be tokenized in the future backgroundColor: 'rgba(255, 255, 255, 0.6)', zIndex: 1, + ...props.overrides?.BaseButton?.style, }, }, }; return ( - <Button kind={KIND.secondary} shape={SHAPE.circle} overrides={overrides} {...props}> + <Button kind={KIND.secondary} shape={SHAPE.circle} {...props} overrides={overrides}> <Delete size={36} /> </Button> ); @@ -73,6 +84,7 @@ const Dialog = ({ placement = PLACEMENT.center, numHeadingLines = 2, size = SIZE.xSmall, + autoFocus = true, }: DialogProps) => { const [Root, rootProps] = getOverrides(overrides.Root, StyledRoot); const [ScrollContainer, scrollContainerProps] = getOverrides( @@ -80,95 +92,74 @@ const Dialog = ({ StyledScrollContainer ); const [Heading, headingProps] = getOverrides(overrides.Heading, StyledHeading); + const [Overlay, overlayProps] = getOverrides(overrides.Overlay, StyledOverlay); const [Body, bodyProps] = getOverrides(overrides.Body, StyledBody); const [ButtonDock, buttonDockProps] = getOverrides(overrides.ButtonDock, DefaultButtonDock); const [DismissButton, dismissButtonProps] = getOverrides( overrides.DismissButton, DefaultDismissButton ); - - const dialogRef = React.useRef<HTMLDialogElement>(null); - - // controls the dialog's open/close state - React.useEffect(() => { - if (isOpen) { - if (hasOverlay) { - dialogRef.current?.showModal(); - document.body.style.overflow = 'hidden'; - } else { - dialogRef.current?.show(); - } - } else { - dialogRef.current?.close(); - } - }, [isOpen, hasOverlay]); + const overlayRef = useRef<HTMLDialogElement>(null); // prevents background scrolling when the dialog is open and has an overlay - const originalOverflowRef = React.useRef<string>(document.body.style.overflow); - React.useEffect(() => { + const originalOverflowRef = useRef<string>( + typeof document !== 'undefined' ? document?.body?.style?.overflow : '' + ); + useEffect(() => { const originalOverflow = originalOverflowRef.current; - if (isOpen && hasOverlay) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = originalOverflow; } - return () => { document.body.style.overflow = originalOverflow; }; }, [isOpen, hasOverlay]); function handleOutsideClick(e) { - if (!handleDismiss) return; - if (!dialogRef.current?.contains(e.target) || e.target.nodeName === 'DIALOG') { + if ( + e.target && + e.target instanceof HTMLElement && + e.target.contains(overlayRef.current) && + handleDismiss + ) { handleDismiss(); } } - function handleEscapeKey(e) { - if (!handleDismiss) return; - if (e.key === 'Escape') { - handleDismiss(); - } + function handleEscape() { + handleDismiss && handleDismiss(); } - React.useEffect(() => { - if (isOpen) { - // No delay, just pushing it to the end of the call stack so that the - // Dialog doesn't close immediately after opening via click event - const timer = setTimeout(() => { - document.addEventListener('click', handleOutsideClick); - }, 0); - document.addEventListener('keydown', handleEscapeKey); - - return () => { - clearTimeout(timer); - document.removeEventListener('click', handleOutsideClick); - document.removeEventListener('keydown', handleEscapeKey); - }; - } else { - document.removeEventListener('click', handleOutsideClick); - document.removeEventListener('keydown', handleEscapeKey); - } - }, [isOpen]); - - return ( - <Root ref={dialogRef} $isOpen={isOpen} $size={size} $placement={placement} {...rootProps}> - {handleDismiss && showDismissButton && ( - <DismissButton onClick={() => handleDismiss()} {...dismissButtonProps} /> - )} - - <ScrollContainer {...scrollContainerProps} tabIndex={0}> - {renderArtwork(artwork)} - <Heading $numHeadingLines={numHeadingLines} {...headingProps}> - {heading} - </Heading> - <Body {...bodyProps}>{children}</Body> - </ScrollContainer> - - {buttonDock && <ButtonDock {...buttonDock} {...buttonDockProps} />} - </Root> - ); + return isOpen ? ( + <Layer onDocumentClick={handleOutsideClick} onEscape={handleEscape}> + {hasOverlay && <Overlay ref={overlayRef} {...overlayProps} />} + + <FocusLock returnFocus={true} autoFocus={autoFocus}> + <Root + $size={size} + $placement={placement} + role="dialog" + aria-labelledby="dialog-title" + {...rootProps} + > + {handleDismiss && showDismissButton && ( + <DismissButton onClick={() => handleDismiss()} {...dismissButtonProps} /> + )} + + <ScrollContainer {...scrollContainerProps} tabIndex={0}> + {renderArtwork(artwork)} + <Heading $numHeadingLines={numHeadingLines} id="dialog-title" {...headingProps}> + {heading} + </Heading> + <Body {...bodyProps}>{children}</Body> + </ScrollContainer> + + {buttonDock && <ButtonDock {...buttonDock} {...buttonDockProps} />} + </Root> + </FocusLock> + </Layer> + ) : null; }; export default Dialog; diff --git a/src/dialog/styled-components.ts b/src/dialog/styled-components.ts index cc6cedd10b..b0c1cd5f2f 100644 --- a/src/dialog/styled-components.ts +++ b/src/dialog/styled-components.ts @@ -31,61 +31,99 @@ const getPlacementStyles = (placement: Placement, gutter: string) => { } }; +const getAnimationStyles = (placement: Placement) => { + const transformValuesByPlacement = { + [PLACEMENT.topLeft]: ['translateY(16px)', 'translateY(0px)'], + [PLACEMENT.topCenter]: [ + 'translateX(-50%) translateY(16px)', + 'translateX(-50%) translateY(0px)', + ], + [PLACEMENT.topRight]: ['translateY(16px)', 'translateY(0px)'], + [PLACEMENT.bottomLeft]: ['translateY(16px)', 'translateY(0px)'], + [PLACEMENT.bottomCenter]: [ + 'translateX(-50%) translateY(16px)', + 'translateX(-50%) translateY(0px)', + ], + [PLACEMENT.bottomRight]: ['translateY(16px)', 'translateY(0px)'], + [PLACEMENT.center]: [ + 'translateX(-50%) translateY(calc(-50% + 16px))', + 'translateX(-50%) translateY(-50%)', + ], + }; + + return { + animationDuration: '400ms', + animationTimingFunction: 'cubic-bezier(0.22, 1, 0.36, 1)', + animationName: { + from: { + opacity: 0, + transform: transformValuesByPlacement[placement][0], + }, + to: { + opacity: 1, + transform: transformValuesByPlacement[placement][1], + }, + }, + }; +}; + const DIALOG_WIDTHS = { [SIZE.xSmall]: '480px', [SIZE.small]: '640px', [SIZE.medium]: '800px', [SIZE.large]: '100%', }; -export const StyledRoot = styled< - 'dialog', - { $size: Size; $placement: Placement; $isOpen: boolean } ->('dialog', ({ $theme, $size, $placement = PLACEMENT.center, $isOpen }) => { - const narrowViewportGutter = '16px'; - const wideViewportGutter = '40px'; +export const StyledRoot = styled<'div', { $size: Size; $placement: Placement }>( + 'div', + ({ $theme, $size, $placement = PLACEMENT.center }) => { + const narrowViewportGutter = '16px'; + const wideViewportGutter = '40px'; - return { - position: 'fixed', - maxHeight: `calc(100vh - (2 * ${wideViewportGutter}))`, - maxWidth: `calc(100% - (2 * ${wideViewportGutter}))`, - borderRadius: $theme.borders.radius500, - boxShadow: $theme.lighting.shallowBelow, - backgroundColor: $theme.colors.backgroundPrimary, - color: $theme.colors.contentPrimary, - overflow: 'hidden', - border: 'none', - width: DIALOG_WIDTHS[$size], - ...getPlacementStyles($placement, wideViewportGutter), - '@media (max-width: 599px)': { - width: `calc(100% - (2 * ${narrowViewportGutter}))`, - maxWidth: 'none', - ...getPlacementStyles(PLACEMENT.bottomCenter, narrowViewportGutter), + return { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + position: 'fixed', + maxHeight: `calc(100vh - (2 * ${wideViewportGutter}))`, + maxWidth: `calc(100% - (2 * ${wideViewportGutter}))`, + borderRadius: $theme.borders.radius500, + boxShadow: $theme.lighting.shallowBelow, + backgroundColor: $theme.colors.backgroundPrimary, + color: $theme.colors.contentPrimary, + overflow: 'hidden', + border: 'none', + width: DIALOG_WIDTHS[$size], + ...getPlacementStyles($placement, wideViewportGutter), + ...getAnimationStyles($placement), + '@media (max-width: 599px)': { + width: `calc(100% - (2 * ${narrowViewportGutter}))`, + maxWidth: 'none', + ...getPlacementStyles(PLACEMENT.bottomCenter, narrowViewportGutter), + ...getAnimationStyles(PLACEMENT.bottomCenter), + }, + }; + } +); +StyledRoot.displayName = 'StyledRoot'; + +export const StyledOverlay = styled('div', ({ $theme }) => ({ + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + backgroundColor: $theme.colors.backgroundOverlay, + animationDuration: '100ms', + animationName: { + from: { + opacity: 0, }, - '::backdrop': { - backgroundColor: $theme.colors.backgroundOverlay, + to: { + opacity: 1, }, - - // Dialog style resets - marginTop: '0px', - marginRight: '0px', - marginBottom: '0px', - marginLeft: '0px', - paddingTop: '0px', - paddingRight: '0px', - paddingBottom: '0px', - paddingLeft: '0px', - - // modifying the default `display` value for a closed dialog element causes unexpected behavior - ...($isOpen - ? { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - } - : {}), - }; -}); -StyledRoot.displayName = 'StyledRoot'; + }, +})); +StyledOverlay.displayName = 'StyledOverlay'; export const StyledScrollContainer = styled<'div', {}>('div', () => { return { diff --git a/src/dialog/types.ts b/src/dialog/types.ts index 16d53b7aae..d14f3c23dd 100644 --- a/src/dialog/types.ts +++ b/src/dialog/types.ts @@ -12,6 +12,7 @@ import type { SIZE, PLACEMENT } from './constants'; export type DialogOverrides = { Root?: Override; + Overlay?: Override; ScrollContainer?: Override; Heading?: Override; Body?: Override; @@ -43,4 +44,6 @@ export type DialogProps = { /** Determines where on the screen the dialog appears when open */ placement?: Placement; size?: Size; + /** If true, focus will shift to the first interactive element within the modal */ + autoFocus?: boolean; }; diff --git a/src/file-uploader-beta/__tests__/file-uploader-beta.test.tsx b/src/file-uploader-beta/__tests__/file-uploader-beta.test.tsx new file mode 100644 index 0000000000..75d4a65bf0 --- /dev/null +++ b/src/file-uploader-beta/__tests__/file-uploader-beta.test.tsx @@ -0,0 +1,527 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import * as React from 'react'; +import { + act, + fireEvent, + getAllByTestId, + getByTestId, + getByText, + render, + screen, + waitFor, +} from '@testing-library/react'; +import { FileUploaderBeta } from '..'; + +describe('FileUploaderBeta', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders gracefully', () => { + const { container } = render(<FileUploaderBeta />); + expect(container).toBeDefined(); + }); + + it('throws errors when invalid props are passed in', () => { + const original = console.error; + console.error = jest.fn(); + render( + <FileUploaderBeta + // @ts-ignore + onDrop={() => {}} + onDropAccepted={() => {}} + onDropRejected={() => {}} + /> + ); + expect(console.error).toHaveBeenCalledTimes(3); + expect(console.error).toHaveBeenNthCalledWith(1, 'onDrop is not a prop for FileUploaderBeta.'); + expect(console.error).toHaveBeenNthCalledWith( + 2, + 'onDropAccepted is not a prop for FileUploaderBeta.' + ); + expect(console.error).toHaveBeenNthCalledWith( + 3, + 'onDropRejected is not a prop for FileUploaderBeta.' + ); + console.error = original; + }); + + it('renders file rows when passed in props', () => { + const mockFileAdded = new File(['(⌐□_□)'], 'test.jpg', { + type: 'image/jpg', + }); + const mockFileError = new File(['(⌐□_□)'], 'test.pdf', { + type: 'application/pdf', + }); + const mockFileProcessed = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const { container } = render( + <FileUploaderBeta + fileRows={[ + { + errorMessage: null, + file: mockFileAdded, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'added', + }, + { + errorMessage: 'custom error message', + file: mockFileError, + imagePreviewThumbnail: '', + status: 'error', + }, + { + errorMessage: null, + file: mockFileProcessed, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'processed', + }, + ]} + setFileRows={() => {}} + /> + ); + getByText(container, 'test.jpg'); + getByText(container, 'Loading...'); + getByText(container, 'test.pdf'); + getByText(container, 'Upload failed: custom error message'); + getByText(container, 'test.png'); + getByText(container, 'Upload successful'); + }); + + it('calls setFileRows for accepted and rejected files and does not call invalid props', async () => { + const original = console.error; + console.error = jest.fn(); + const mockAcceptedFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockRejectedFile = new File(['(⌐□_□)'], 'test.json', { + type: 'application/json', + }); + const files = [mockAcceptedFile, mockRejectedFile]; + + const mockFileRows = []; + const mockSetFileRows = jest.fn(); + const onDropMock = jest.fn(); + const onDropAcceptedMock = jest.fn(); + const onDropRejectedMock = jest.fn(); + render( + <FileUploaderBeta + fileRows={mockFileRows} + setFileRows={mockSetFileRows} + // @ts-ignore + onDrop={onDropMock} + onDropAccepted={onDropAcceptedMock} + onDropRejected={onDropRejectedMock} + /> + ); + + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { target: { files } }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(5); + expect(mockSetFileRows).toHaveBeenNthCalledWith(5, [ + { + errorMessage: null, + file: mockAcceptedFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'processed', + }, + { + errorMessage: null, + file: mockRejectedFile, + imagePreviewThumbnail: '', + status: 'processed', + }, + ]); + + // Expect invalid props to not have been called + expect(onDropMock).not.toHaveBeenCalled(); + expect(onDropAcceptedMock).not.toHaveBeenCalled(); + expect(onDropRejectedMock).not.toHaveBeenCalled(); + }); + console.error = original; + }); + + it('renders hint if provided', () => { + const { container } = render( + <FileUploaderBeta fileRows={[]} hint={'test hint'} setFileRows={() => {}} /> + ); + getByText(container, 'test hint'); + }); + + it('renders label if provided', () => { + const { container } = render( + <FileUploaderBeta fileRows={[]} label={'test label'} setFileRows={() => {}} /> + ); + getByText(container, 'test label'); + }); + + it('renders label if disabled', () => { + const { container } = render( + <FileUploaderBeta disabled fileRows={[]} label={'test label'} setFileRows={() => {}} /> + ); + getByText(container, 'test label'); + }); + + it('calls setFilesRows with error when too many files are added', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta fileRows={[]} maxFiles={0} setFileRows={mockSetFileRows} />); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'cannot process more than 0 file(s)', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + }); + + it('calls setFilesRows with error when file size is too small', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta fileRows={[]} minSize={50 * 1000} setFileRows={mockSetFileRows} />); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'file size must be greater than 50 KB', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + }); + + it('calls setFilesRows with error when file size is too big', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta fileRows={[]} maxSize={0} setFileRows={mockSetFileRows} />); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'file size must be less than 0 bytes', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + }); + + it('calls setFilesRows with error when file type is not accepted', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta accept={'image/jpg'} fileRows={[]} setFileRows={mockSetFileRows} />); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'file type of image/png is not accepted', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + }); + + it('calls setFilesRows with error when processFileOnDrop returns with an error', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockProcessFileOnDrop = jest.fn((file) => + Promise.resolve({ errorMessage: 'test error' }) + ); + const mockSetFileRows = jest.fn(); + render( + <FileUploaderBeta + fileRows={[]} + processFileOnDrop={mockProcessFileOnDrop} + setFileRows={mockSetFileRows} + /> + ); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockProcessFileOnDrop).toHaveBeenCalledTimes(1); + expect(mockProcessFileOnDrop).toHaveBeenCalledWith(mockFile); + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'test error', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + }); + + it('calls setFilesRows with success when processFileOnDrop returns with success', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockProcessFileOnDrop = jest.fn((file) => + Promise.resolve({ errorMessage: null, fileInfo: 'test-file-info' }) + ); + const mockSetFileRows = jest.fn(); + render( + <FileUploaderBeta + fileRows={[]} + processFileOnDrop={mockProcessFileOnDrop} + setFileRows={mockSetFileRows} + /> + ); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockProcessFileOnDrop).toHaveBeenCalledTimes(1); + expect(mockProcessFileOnDrop).toHaveBeenCalledWith(mockFile); + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: null, + file: mockFile, + fileInfo: 'test-file-info', + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'processed', + }, + ]); + }); + }); + + it('calls setFilesRows with error when processFileOnDrop returns with undefined error', async () => { + const original = console.error; + console.error = jest.fn(); + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockError = new Error(); + const mockProcessFileOnDrop = jest.fn((file) => Promise.reject(mockError)); + const mockSetFileRows = jest.fn(); + render( + <FileUploaderBeta + fileRows={[]} + processFileOnDrop={mockProcessFileOnDrop} + setFileRows={mockSetFileRows} + /> + ); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockProcessFileOnDrop).toHaveBeenCalledTimes(1); + expect(mockProcessFileOnDrop).toHaveBeenCalledWith(mockFile); + expect(console.error).toHaveBeenCalledTimes(1); + expect(console.error).toHaveBeenCalledWith('error with processFileOnDrop', mockError); + expect(mockSetFileRows).toHaveBeenCalledTimes(3); + expect(mockSetFileRows).toHaveBeenNthCalledWith(3, [ + { + errorMessage: 'unknown processing error', + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'error', + }, + ]); + }); + console.error = original; + }); + + it('prevents drop when disabled is true', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockError = new Error(); + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta disabled fileRows={[]} setFileRows={mockSetFileRows} />); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(0); + }); + }); + + it('prevents drop when files are loading and disabled is false', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockError = new Error(); + const mockSetFileRows = jest.fn(); + render( + <FileUploaderBeta + disabled={false} + fileRows={[ + { + errorMessage: null, + file: mockFile, + imagePreviewThumbnail: '', + status: 'added', + }, + ]} + setFileRows={mockSetFileRows} + /> + ); + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { + target: { files: [mockFile] }, + }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(0); + }); + }); + + it('calls setFileRows when FileReader onerror is triggered', async () => { + // @ts-ignore + jest.spyOn(global, 'FileReader').mockImplementationOnce(function () { + this.readAsDataURL = jest.fn(() => { + if (this.onerror) { + const errorEvent = new Event('error'); + this.onerror(errorEvent); + } + }); + }); + + const mockFile = new File(['(⌐□_□)'], 'test.json', { + type: 'application/json', + }); + const files = [mockFile]; + + const mockFileRows = []; + const mockSetFileRows = jest.fn(); + render(<FileUploaderBeta fileRows={mockFileRows} setFileRows={mockSetFileRows} />); + + await act(async () => { + await fireEvent.drop(screen.getByText('Drop files here to upload...'), { target: { files } }); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(2); + expect(mockSetFileRows).toHaveBeenNthCalledWith(2, [ + { + errorMessage: 'cannot read file', + file: mockFile, + imagePreviewThumbnail: '', + status: 'error', + }, + ]); + }); + }); + + it('calls setFileRows when the remove file icon is clicked', async () => { + const mockFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const mockSetFileRows = jest.fn(); + const { container } = render( + <FileUploaderBeta + fileRows={[ + { + errorMessage: null, + file: mockFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'processed', + }, + ]} + setFileRows={mockSetFileRows} + overrides={{ TrashCanFilledIconContainer: { props: { 'data-testid': 'trash-can' } } }} + /> + ); + + await act(async () => { + await fireEvent.click(getByTestId(container, 'trash-can')); + }); + await waitFor(() => { + expect(mockSetFileRows).toHaveBeenCalledTimes(1); + expect(mockSetFileRows).toHaveBeenCalledWith([]); + }); + }); + + it('shows item preview when itemPreview is true', () => { + const mockDocumentFile = new File(['(⌐□_□)'], 'test.pdf', { + type: 'application/pdf', + }); + const mockImageFile = new File(['(⌐□_□)'], 'test.png', { + type: 'image/png', + }); + const { container } = render( + <FileUploaderBeta + fileRows={[ + { + errorMessage: null, + file: mockDocumentFile, + imagePreviewThumbnail: '', + status: 'processed', + }, + { + errorMessage: null, + file: mockImageFile, + imagePreviewThumbnail: 'data:image/png;base64,KOKMkOKWoV/ilqEp', + status: 'processed', + }, + ]} + itemPreview + setFileRows={() => {}} + overrides={{ + ImagePreviewThumbnail: { props: { 'data-testid': 'image-thumbnail' } }, + ItemPreviewContainer: { props: { 'data-testid': 'item-preview' } }, + PaperclipFilledIcon: { props: { 'data-testid': 'paperclip' } }, + }} + /> + ); + getByTestId(container, 'image-thumbnail'); + getAllByTestId(container, 'item-preview'); + getByTestId(container, 'paperclip'); + }); +}); diff --git a/src/file-uploader-beta/constants.ts b/src/file-uploader-beta/constants.ts new file mode 100644 index 0000000000..0e7e400d04 --- /dev/null +++ b/src/file-uploader-beta/constants.ts @@ -0,0 +1,21 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import type { Theme } from '../styles'; + +export const FILE_STATUS = { + added: 'added', + error: 'error', + processed: 'processed', +} as const; + +export const FILE_STATUS_TO_COLOR_MAP = ( + $theme: Theme +): { added: string; error: string; processed: string } => ({ + [FILE_STATUS.added]: $theme.colors.accent, + [FILE_STATUS.error]: $theme.colors.negative, + [FILE_STATUS.processed]: $theme.colors.positive, +}); diff --git a/src/file-uploader-beta/file-uploader-beta.tsx b/src/file-uploader-beta/file-uploader-beta.tsx new file mode 100644 index 0000000000..a192ea0317 --- /dev/null +++ b/src/file-uploader-beta/file-uploader-beta.tsx @@ -0,0 +1,489 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import * as React from 'react'; +import { FileUploader } from '../file-uploader'; +import { SHAPE, SIZE } from '../button'; +import { ProgressBar } from '../progress-bar'; +import { getOverrides } from '../helpers/overrides'; +import { + StyledFileRow, + StyledFileRowColumn, + StyledFileRowContent, + StyledFileRowFileName, + StyledFileRowText, + StyledFileRowUploadMessage, + StyledFileRowUploadText, + StyledFileRows, + StyledHint, + StyledImagePreviewThumbnail, + StyledItemPreviewContainer, + StyledLabel, + StyledParentRoot, + StyledTrashCanFilledIconContainer, +} from './styled-components'; +import { FILE_STATUS, FILE_STATUS_TO_COLOR_MAP } from './constants'; +import { formatBytes } from './utils'; +import AlertIconComponent from '../icon/alert'; +import CircleCheckFilledIconComponent from '../icon/circle-check-filled'; +import PaperclipFilledIconComponent from '../icon/paperclip-filled'; +import TrashCanFilledIconComponent from '../icon/trash-can-filled'; +import { useStyletron } from '../styles'; +import { LocaleContext } from '../locale'; + +import type { FileRow, FileUploaderBetaProps } from './types'; + +export default function FileUploaderBeta(props: FileUploaderBetaProps) { + if (props['onDrop']) { + console.error('onDrop is not a prop for FileUploaderBeta.'); + } + if (props['onDropAccepted']) { + console.error('onDropAccepted is not a prop for FileUploaderBeta.'); + } + if (props['onDropRejected']) { + console.error('onDropRejected is not a prop for FileUploaderBeta.'); + } + + // Isolate props that are not meant to be passed to FileUploaderBasic + const { + fileRows, + hint, + itemPreview, + label, + maxFiles, + overrides = {}, + processFileOnDrop, + setFileRows, + ...fileUploaderBasicProps + } = props; + // Isolate styles that are not meant to be passed to FileUploaderBasic + const { + // Overrides for FileUploaderBeta + AlertIcon: OverridesAlertIcon, + CircleCheckFilledIcon: OverridesCircleCheckFilledIcon, + FileRow: OverridesFileRow, + FileRowColumn: OverridesFileRowColumn, + FileRowContent: OverridesFileRowContent, + FileRowFileName: OverridesFileRowFileName, + FileRowText: OverridesFileRowText, + FileRowUploadMessage: OverridesFileRowUploadMessage, + FileRowUploadText: OverridesFileRowUploadText, + FileRows: OverridesFileRows, + Hint: OverridesHint, + ImagePreviewThumbnail: OverridesImagePreviewThumbnail, + ItemPreviewContainer: OverridesItemPreviewContainer, + Label: OverridesLabel, + PaperclipFilledIcon: OverridesPaperclipFilledIcon, + ParentRoot: OverridesParentRoot, + TrashCanFilledIcon: OverridesTrashCanFilledIcon, + TrashCanFilledIconContainer: OverridesTrashCanFilledIconContainer, + + // Overrides for FileUploaderBasic that are modified in this file + ButtonComponent, + ContentMessage, + FileDragAndDrop, + ...fileUploaderBasicOverrides + } = overrides; + + const [, theme] = useStyletron(); + + // Prepare icon overrides + const [AlertIcon, alertIconProps] = getOverrides(OverridesAlertIcon, AlertIconComponent); + const [CircleCheckFilledIcon, circleCheckFilledIconProps] = getOverrides( + overrides.CircleCheckFilledIcon, + CircleCheckFilledIconComponent + ); + const [PaperclipFilledIcon, paperclipFilledIconProps] = getOverrides( + OverridesPaperclipFilledIcon, + PaperclipFilledIconComponent + ); + const [TrashCanFilledIcon, trashCanFilledIconProps] = getOverrides( + OverridesTrashCanFilledIcon, + TrashCanFilledIconComponent + ); + + // Prepare styled component overrides + const [FileRow, fileRowProps] = getOverrides(OverridesFileRow, StyledFileRow); + const [FileRowColumn, fileRowColumnProps] = getOverrides( + OverridesFileRowColumn, + StyledFileRowColumn + ); + const [FileRowContent, fileRowContentProps] = getOverrides( + OverridesFileRowContent, + StyledFileRowContent + ); + const [FileRowFileName, fileRowFileNameProps] = getOverrides( + OverridesFileRowFileName, + StyledFileRowFileName + ); + const [FileRowText, fileRowTextProps] = getOverrides(OverridesFileRowText, StyledFileRowText); + const [FileRowUploadMessage, fileRowUploadMessageProps] = getOverrides( + OverridesFileRowUploadMessage, + StyledFileRowUploadMessage + ); + const [FileRowUploadText, fileRowUploadTextProps] = getOverrides( + OverridesFileRowUploadText, + StyledFileRowUploadText + ); + const [FileRows, fileRowsProps] = getOverrides(OverridesFileRows, StyledFileRows); + const [Hint, hintProps] = getOverrides(OverridesHint, StyledHint); + const [ImagePreviewThumbnail, imagePreviewThumbnailProps] = getOverrides( + OverridesImagePreviewThumbnail, + StyledImagePreviewThumbnail + ); + const [ItemPreviewContainer, itemPreviewContainerProps] = getOverrides( + OverridesItemPreviewContainer, + StyledItemPreviewContainer + ); + const [Label, labelProps] = getOverrides(OverridesLabel, StyledLabel); + const [ProgressBarComponent, progressBarProps] = getOverrides(overrides.ProgressBar, ProgressBar); + const [ParentRoot, parentRootProps] = getOverrides(OverridesParentRoot, StyledParentRoot); + const [TrashCanFilledIconContainer, trashCanFilledIconContainerProps] = getOverrides( + OverridesTrashCanFilledIconContainer, + StyledTrashCanFilledIconContainer + ); + + const onDrop = React.useCallback( + (acceptedFiles: Array<File>, rejectedFiles: Array<File>) => { + const newFiles = acceptedFiles.concat(rejectedFiles); + let newFileRows = [...props.fileRows]; + newFiles.forEach((file: File) => { + newFileRows.push({ + errorMessage: null, + file, + imagePreviewThumbnail: '', + status: FILE_STATUS.added, + }); + props.setFileRows([...newFileRows]); + }); + + newFileRows.forEach((fileRow: FileRow, index: number) => { + if (fileRow.status === FILE_STATUS.added) { + let reader = new FileReader(); + + reader.onerror = () => { + newFileRows[index].errorMessage = 'cannot read file'; + newFileRows[index].status = FILE_STATUS.error; + props.setFileRows([...newFileRows]); + }; + + reader.onload = (event) => { + if (newFileRows[index].file.type.startsWith('image/')) { + newFileRows[index].imagePreviewThumbnail = event.target?.result; + props.setFileRows([...newFileRows]); + } + if ( + props.maxFiles !== undefined && + Number.isInteger(props.maxFiles) && + index >= props.maxFiles + ) { + // If too many files + newFileRows[ + index + ].errorMessage = `cannot process more than ${props.maxFiles} file(s)`; + newFileRows[index].status = FILE_STATUS.error; + props.setFileRows([...newFileRows]); + } else if ( + props.minSize !== undefined && + Number.isInteger(props.minSize) && + props.minSize > fileRow.file.size + ) { + // If file size is too small + newFileRows[index].errorMessage = `file size must be greater than ${formatBytes( + props.minSize + )}`; + newFileRows[index].status = FILE_STATUS.error; + props.setFileRows([...newFileRows]); + } else if ( + props.maxSize !== undefined && + Number.isInteger(props.maxSize) && + props.maxSize < fileRow.file.size + ) { + // If file size is too big + newFileRows[index].errorMessage = `file size must be less than ${formatBytes( + props.maxSize + )}`; + newFileRows[index].status = FILE_STATUS.error; + props.setFileRows([...newFileRows]); + } else if (index >= newFileRows.length - rejectedFiles.length) { + // If file was rejected by dropzone (e.g. wrong file type) + newFileRows[index].errorMessage = `file type of ${fileRow.file.type} is not accepted`; + newFileRows[index].status = FILE_STATUS.error; + props.setFileRows([...newFileRows]); + } else if (props.processFileOnDrop) { + // If caller passed in file process function + props + .processFileOnDrop(fileRow.file) + .then( + ({ errorMessage, fileInfo }: { errorMessage: string | null; fileInfo?: any }) => { + if (fileInfo) { + newFileRows[index].fileInfo = fileInfo; + } + if (errorMessage) { + newFileRows[index].errorMessage = errorMessage; + newFileRows[index].status = FILE_STATUS.error; + } else { + newFileRows[index].status = FILE_STATUS.processed; + } + } + ) + .catch((error) => { + console.error('error with processFileOnDrop', error); + newFileRows[index].errorMessage = 'unknown processing error'; + newFileRows[index].status = FILE_STATUS.error; + }) + .finally(() => { + props.setFileRows([...newFileRows]); + }); + } else { + // If no errors and no file process function + newFileRows[index].status = FILE_STATUS.processed; + props.setFileRows([...newFileRows]); + } + }; + + reader.readAsDataURL(fileRow.file); + } + }); + }, + [props] + ); + + const removeFileRow = (event: React.MouseEvent) => { + event.preventDefault(); + const indexOfFileRowToRemove = Number(event?.currentTarget?.getAttribute('index')); + props.setFileRows([...props.fileRows.toSpliced(indexOfFileRowToRemove, 1)]); + }; + + return ( + <ParentRoot data-baseweb="file-uploader-beta-parent-root" {...parentRootProps}> + {props.label && ( + <Label data-baseweb="file-uploader-beta-label" {...labelProps} $disabled={!!props.disabled}> + {props.label} + </Label> + )} + <FileUploader + overrides={{ + ButtonComponent: { + props: { + shape: SHAPE.default, + size: SIZE.default, + ...props.overrides?.ButtonComponent?.props, + style: { + marginTop: 0, + // @ts-expect-error + ...props.overrides?.ButtonComponent?.props?.style, + }, + overrides: { + // @ts-expect-error + ...props.overrides?.ButtonComponent?.props?.overrides, + BaseButton: { + // @ts-expect-error + ...props.overrides?.ButtonComponent?.props?.overrides?.BaseButton, + style: { + backgroundColor: theme.colors.backgroundPrimary, + // @ts-expect-error + ...props.overrides?.ButtonComponent?.props?.overrides?.BaseButton?.style, + }, + }, + }, + }, + }, + ContentMessage: { + style: { + ...theme.typography.ParagraphMedium, + color: theme.colors.contentTertiary, + ...props.overrides?.ContentMessage?.style, + }, + }, + FileDragAndDrop: { + style: { + justifyContent: 'flex-end', + flexDirection: 'row-reverse', + flexWrap: 'wrap', + gap: theme.sizing.scale300, + paddingBottom: theme.sizing.scale600, + paddingLeft: theme.sizing.scale600, + paddingRight: theme.sizing.scale600, + paddingTop: theme.sizing.scale600, + ...props.overrides?.FileDragAndDrop?.style, + }, + }, + }} + {...fileUploaderBasicOverrides} + {...fileUploaderBasicProps} + // Disable uploads while files are loading, even if application passes disabled as false + disabled={ + !!props.disabled + ? props.disabled + : !!props.fileRows.find((fileRow: FileRow) => fileRow.status === FILE_STATUS.added) + } + // Implement or use no-op callbacks to prevent consumers from passing them in + onDrop={onDrop} + onDropAccepted={(_: Array<File>) => {}} + onDropRejected={(_: Array<File>) => {}} + /> + {props.fileRows.length > 0 && ( + <LocaleContext.Consumer> + {(locale) => ( + <FileRows data-baseweb="file-uploader-beta-file-rows" {...fileRowsProps}> + {props.fileRows.map((fileRow, index) => ( + <FileRow + id={`file-uploader-beta-file-row-${index}`} + data-baseweb="file-uploader-beta-file-row" + key={index} + {...fileRowProps} + > + {props.itemPreview && ( + <ItemPreviewContainer + data-baseweb="file-uploader-beta-item-preview-container" + {...itemPreviewContainerProps} + > + {fileRow.imagePreviewThumbnail ? ( + <ImagePreviewThumbnail + alt={fileRow.file.name} + data-baseweb="file-uploader-beta-image-preview-thumbnail" + src={fileRow.imagePreviewThumbnail} + {...imagePreviewThumbnailProps} + /> + ) : ( + <PaperclipFilledIcon + data-baseweb="file-uploader-beta-paperclip-filled-icon" + color={theme.colors.contentSecondary} + {...paperclipFilledIconProps} + /> + )} + </ItemPreviewContainer> + )} + <FileRowColumn + data-baseweb="file-uploader-beta-file-row-column" + {...fileRowColumnProps} + > + <FileRowContent + data-baseweb="file-uploader-beta-file-row-content" + {...fileRowContentProps} + > + <FileRowText + data-baseweb="file-uploader-beta-file-row-text" + {...fileRowTextProps} + > + <FileRowFileName + data-baseweb="file-uploader-beta-file-row-file-name" + {...fileRowFileNameProps} + > + {fileRow.file.name} + </FileRowFileName> + <FileRowUploadMessage + data-baseweb="file-uploader-beta-file-row-upload-message" + $color={FILE_STATUS_TO_COLOR_MAP(theme)[fileRow.status]} + {...fileRowUploadMessageProps} + > + {fileRow.status === FILE_STATUS.error && ( + <> + <AlertIcon + color={FILE_STATUS_TO_COLOR_MAP(theme)[fileRow.status]} + data-baseweb="file-uploader-beta-alert-icon" + title={fileRow.status} + {...alertIconProps} + /> + <FileRowUploadText + aria-errormessage={fileRow.errorMessage} + {...fileRowUploadTextProps} + > + {locale.fileuploaderbeta.error} + {fileRow.errorMessage} + </FileRowUploadText> + </> + )} + {fileRow.status === FILE_STATUS.processed && ( + <> + <CircleCheckFilledIcon + color={FILE_STATUS_TO_COLOR_MAP(theme)[fileRow.status]} + data-baseweb="file-uploader-beta-circle-check-filled-icon" + title={fileRow.status} + {...circleCheckFilledIconProps} + /> + <FileRowUploadText {...fileRowUploadTextProps}> + {locale.fileuploaderbeta.processed} + </FileRowUploadText> + </> + )} + {fileRow.status === FILE_STATUS.added && ( + <FileRowUploadText {...fileRowUploadTextProps}> + {locale.fileuploaderbeta.added} + </FileRowUploadText> + )} + </FileRowUploadMessage> + </FileRowText> + {fileRow.status !== FILE_STATUS.added && ( + <TrashCanFilledIconContainer + data-baseweb="file-uploader-beta-trash-can-filled-icon-container" + index={index} + onClick={removeFileRow} + {...trashCanFilledIconContainerProps} + > + <TrashCanFilledIcon + aria-controls={`file-uploader-beta-file-row-${index}`} + aria-describedby={props['aria-describedby']} + aria-label={'remove'} + data-baseweb="file-uploader-beta-trash-can-filled-icon" + overrides={{ Svg: { style: { verticalAlign: 'middle' } } }} + size={theme.sizing.scale600} + title={'remove'} + {...trashCanFilledIconProps} + /> + </TrashCanFilledIconContainer> + )} + </FileRowContent> + <ProgressBarComponent + data-baseweb="file-uploader-beta-progress-bar" + overrides={{ + Bar: { + style: { + marginTop: theme.sizing.scale0, + marginBottom: theme.sizing.scale0, + marginLeft: 0, + marginRight: 0, + }, + }, + BarContainer: { + style: { + marginTop: 0, + marginBottom: 0, + marginLeft: 0, + marginRight: 0, + }, + }, + BarProgress: { + // @ts-ignore + style: ({ $theme }) => ({ + backgroundColor: FILE_STATUS_TO_COLOR_MAP($theme)[fileRow.status], + }), + }, + }} + value={fileRow.status === FILE_STATUS.processed ? 100 : 20} + {...progressBarProps} + /> + </FileRowColumn> + </FileRow> + ))} + </FileRows> + )} + </LocaleContext.Consumer> + )} + {props.hint && ( + <Hint data-baseweb="file-uploader-beta-hint" {...hintProps}> + {props.hint} + </Hint> + )} + </ParentRoot> + ); +} + +FileUploaderBeta.defaultProps = { + fileRows: [], + setFileRows: () => {}, +}; diff --git a/src/file-uploader-beta/index.ts b/src/file-uploader-beta/index.ts new file mode 100644 index 0000000000..a6b376537d --- /dev/null +++ b/src/file-uploader-beta/index.ts @@ -0,0 +1,29 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +export { default as FileUploaderBeta } from './file-uploader-beta'; + +// Styled elements +export { + StyledFileRow, + StyledFileRowColumn, + StyledFileRowContent, + StyledFileRowFileName, + StyledFileRowText, + StyledFileRowUploadMessage, + StyledFileRowUploadText, + StyledFileRows, + StyledHint, + StyledImagePreviewThumbnail, + StyledItemPreviewContainer, + StyledLabel, + StyledParentRoot, + StyledTrashCanFilledIconContainer, +} from './styled-components'; + +// Flow +export * from './types'; +export type { FileUploaderBetaLocale } from './locale'; diff --git a/src/file-uploader-beta/locale.ts b/src/file-uploader-beta/locale.ts new file mode 100644 index 0000000000..998da3356f --- /dev/null +++ b/src/file-uploader-beta/locale.ts @@ -0,0 +1,19 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +export type FileUploaderBetaLocale = { + added: string; + error: string; + processed: string; +}; + +const locale = { + added: 'Loading...', + error: 'Upload failed: ', + processed: 'Upload successful', +}; + +export default locale; diff --git a/src/file-uploader-beta/styled-components.ts b/src/file-uploader-beta/styled-components.ts new file mode 100644 index 0000000000..6f0168b44e --- /dev/null +++ b/src/file-uploader-beta/styled-components.ts @@ -0,0 +1,218 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import { styled } from '../styles'; +import type { StyleProps } from './types'; + +export const StyledFileRow = styled<'li', StyleProps>('li', (props) => { + const { + $theme: { sizing }, + } = props; + return { + paddingTop: sizing.scale500, + paddingRight: sizing.scale500, + paddingBottom: sizing.scale500, + paddingLeft: sizing.scale500, + height: 'fit-content', + display: 'flex', + alignItems: 'center', + }; +}); +StyledFileRow.displayName = 'StyledFileRow'; + +export const StyledFileRowColumn = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { sizing }, + } = props; + return { + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + justifyContent: 'space-between', + paddingRight: sizing.scale500, + paddingLeft: sizing.scale500, + flexGrow: 1, + overflow: 'auto', + }; +}); +StyledFileRowColumn.displayName = 'StyledFileRowColumn'; + +export const StyledFileRowContent = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { sizing }, + } = props; + return { + alignItems: 'center', + display: 'flex', + flexDirection: 'row', + gap: sizing.scale500, + justifyContent: 'space-between', + width: '100%', + height: sizing.scale1200, + }; +}); +StyledFileRowContent.displayName = 'StyledFileRowContent'; + +export const StyledFileRowFileName = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { colors, typography }, + } = props; + return { + ...typography.LabelMedium, + color: colors.contentPrimary, + width: '100%', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + }; +}); +StyledFileRowColumn.displayName = 'StyledFileRowColumn'; + +export const StyledFileRowText = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { sizing }, + } = props; + return { + display: 'flex', + flexDirection: 'column', + height: '100%', + width: '100%', + gap: sizing.scale300, + flexGrow: 1, + overflow: 'auto', + }; +}); +StyledFileRowColumn.displayName = 'StyledFileRowColumn'; + +export const StyledFileRowUploadMessage = styled<'div', StyleProps>('div', (props) => { + const { + $color, + $theme: { sizing, typography }, + } = props; + return { + ...typography.ParagraphSmall, + color: $color, + alignItems: 'center', + gap: sizing.scale100, + display: 'flex', + flexDirection: 'row', + }; +}); +StyledFileRowUploadMessage.displayName = 'StyledFileRowUploadMessage'; + +export const StyledFileRowUploadText = styled<'div', StyleProps>('div', () => { + return { + width: '100%', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + }; +}); +StyledFileRowUploadText.displayName = 'StyledFileRowUploadText'; + +export const StyledFileRows = styled<'ul', StyleProps>('ul', (props) => { + const { + $theme: { borders, sizing }, + } = props; + return { + ...borders.border200, + borderRadius: borders.radius400, + borderWidth: sizing.scale0, + listStyle: 'none', + padding: 0, + marginTop: 0, + marginBottom: 0, + }; +}); +StyledFileRows.displayName = 'StyledFileRows'; + +export const StyledHint = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { colors, typography }, + } = props; + let fontColor = colors.contentTertiary; + return { + ...typography.font100, + color: fontColor, + }; +}); +StyledHint.displayName = 'StyledHint'; + +export const StyledImagePreviewThumbnail = styled<'img', StyleProps>('img', (props) => { + const { + $alt, + $src, + $theme: { borders }, + } = props; + return { + alt: $alt, + ...borders.border200, + borderRadius: borders.radius200, + className: 'thumb', + src: $src, + width: '100%', + height: '100%', + }; +}); +StyledImagePreviewThumbnail.displayName = 'StyledImagePreviewThumbnail'; + +export const StyledItemPreviewContainer = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { borders, colors, sizing }, + } = props; + return { + backgroundColor: colors.backgroundSecondary, + ...borders.border200, + borderRadius: borders.radius200, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + width: sizing.scale1400, + height: sizing.scale1400, + }; +}); +StyledItemPreviewContainer.displayName = 'StyledItemPreviewContainer'; + +export const StyledLabel = styled<'label', StyleProps>('label', (props) => { + const { + $disabled, + $theme: { colors, typography }, + } = props; + return { + ...typography.font250, + width: '100%', + color: $disabled ? colors.contentSecondary : colors.contentPrimary, + display: 'block', + }; +}); +StyledLabel.displayName = 'StyledLabel'; + +export const StyledParentRoot = styled<'div', StyleProps>('div', (props) => { + const { + $theme: { sizing }, + } = props; + return { + display: 'flex', + flexDirection: 'column', + gap: sizing.scale300, + }; +}); +StyledParentRoot.displayName = 'StyledParentRoot'; + +export const StyledTrashCanFilledIconContainer = styled<'span', StyleProps>('span', (props) => { + const { + $theme: { colors }, + } = props; + return { + color: colors.contentPrimary, + cursor: 'pointer', + flexShrink: 0, + }; +}); +StyledTrashCanFilledIconContainer.displayName = 'StyledTrashCanFilledIconContainer'; diff --git a/src/file-uploader-beta/types.ts b/src/file-uploader-beta/types.ts new file mode 100644 index 0000000000..c852271968 --- /dev/null +++ b/src/file-uploader-beta/types.ts @@ -0,0 +1,68 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import type { Override } from '../helpers/overrides'; +import type { FILE_STATUS } from './constants'; +import type { FileUploaderOverrides, FileUploaderProps } from '../file-uploader/types'; + +export type StyleProps = { + $afterFileDrop: boolean; + $alt: string; + $color: string; + $disabled: boolean; + $isDragActive: boolean; + $isDragAccept: boolean; + $isDragReject: boolean; + $isFocused: boolean; + $src: string; +}; + +export type FileUploaderBetaOverrides = { + AlertIcon?: Override; + CircleCheckFilledIcon?: Override; + FileRow?: Override; + FileRowColumn?: Override; + FileRowContent?: Override; + FileRowFileName?: Override; + FileRowText?: Override; + FileRowUploadMessage?: Override; + FileRowUploadText?: Override; + FileRows?: Override; + Hint?: Override; + ImagePreviewThumbnail?: Override; + ItemPreviewContainer?: Override; + Label?: Override; + PaperclipFilledIcon?: Override; + ParentRoot?: Override; + TrashCanFilledIcon?: Override; + TrashCanFilledIconContainer?: Override; +}; + +export type FileRow = { + errorMessage: string | null; + file: File; + // fileInfo is the result of the processFileOnDrop function + // TODO: switch to leverage a generic <T> so applications can define the shape of fileInfo + fileInfo?: any; + imagePreviewThumbnail?: any; + /** Defines the status of a file */ + status: keyof typeof FILE_STATUS; +}; + +export type FileUploaderBetaProps = Omit< + FileUploaderProps, + 'onDrop' | 'onDropAccepted' | 'onDropRejected' | 'overrides' +> & { + fileRows: FileRow[]; + hint?: string; + itemPreview?: boolean; + label?: string; + maxFiles?: number; + overrides?: FileUploaderOverrides & FileUploaderBetaOverrides; + /** Function to run on each file, returns "errorMessage: null" on success and "errorMessage: string" for failures */ + processFileOnDrop?: (file: File) => Promise<{ errorMessage: string | null; fileInfo?: any }>; + setFileRows: (fileRows: FileRow[]) => void; +}; diff --git a/src/file-uploader-beta/utils.ts b/src/file-uploader-beta/utils.ts new file mode 100644 index 0000000000..4f7a9964b0 --- /dev/null +++ b/src/file-uploader-beta/utils.ts @@ -0,0 +1,13 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +export const formatBytes = (bytes: number): string => { + if (bytes === 0) return '0 bytes'; + const k = 1000; + const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; +}; diff --git a/src/file-uploader/__tests__/file-uploader.test.tsx b/src/file-uploader/__tests__/file-uploader.test.tsx index 45344ab041..e5d14ca2a5 100644 --- a/src/file-uploader/__tests__/file-uploader.test.tsx +++ b/src/file-uploader/__tests__/file-uploader.test.tsx @@ -111,4 +111,14 @@ describe('FileUploader', () => { ); getByText(container, message); }); + + it('renders accept attribute as a string when inputted as an array', () => { + const { container } = render(<FileUploader accept={['test-1', 'test-2']} />); + expect(container.querySelector('input')?.getAttribute('accept')).toBe('test-1,test-2'); + }); + + it('renders accept attribute as a string when inputted as a string', () => { + const { container } = render(<FileUploader accept={'test-1,test-2'} />); + expect(container.querySelector('input')?.getAttribute('accept')).toBe('test-1,test-2'); + }); }); diff --git a/src/file-uploader/types.ts b/src/file-uploader/types.ts index 8642135642..93b9e0642a 100644 --- a/src/file-uploader/types.ts +++ b/src/file-uploader/types.ts @@ -10,11 +10,14 @@ import type { Override } from '../helpers/overrides'; export type StyleProps = { $afterFileDrop: boolean; - $isDisabled: boolean; + $alt: string; + $color: string; + $disabled: boolean; $isDragActive: boolean; $isDragAccept: boolean; $isDragReject: boolean; $isFocused: boolean; + $src: string; }; export type FileUploaderOverrides = { diff --git a/src/form-control/form-control.tsx b/src/form-control/form-control.tsx index 9198bd8198..bcfe600f20 100644 --- a/src/form-control/form-control.tsx +++ b/src/form-control/form-control.tsx @@ -8,16 +8,18 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; import { getOverride, getOverrideProps } from '../helpers/overrides'; import { UIDConsumer } from 'react-uid'; +import { Alert, Check } from '../icon'; import { Label as StyledLabel, LabelEndEnhancer as StyledLabelEndEnhancer, LabelContainer as StyledLabelContainer, Caption as StyledCaption, ControlContainer as StyledControlContainer, + CaptionMessage as StyledCaptionMessage, + CaptionIcon as StyledCaptionIcon, } from './styled-components'; import type { FormControlProps, FormControlState, StyleProps } from './types'; -// @ts-ignore function chooseRenderedHint(caption, error, positive, sharedProps) { if (!!error && typeof error !== 'boolean') { return typeof error === 'function' ? error(sharedProps) : error; @@ -34,6 +36,29 @@ function chooseRenderedHint(caption, error, positive, sharedProps) { return null; } +// @ts-ignore +function chooseRenderedHintIcon(hint, error, positive) { + if (!hint) { + return null; + } + + if (!!error) { + if (typeof error === 'function') { + return null; + } + return <Alert size={12} aria-hidden="true" title="" />; + } + + if (!!positive) { + if (typeof positive === 'function') { + return null; + } + return <Check size={12} aria-hidden="true" title="" />; + } + + return null; +} + export default class FormControl extends React.Component<FormControlProps, FormControlState> { static defaultProps = { overrides: {}, @@ -57,6 +82,10 @@ export default class FormControl extends React.Component<FormControlProps, FormC // @ts-ignore Caption: CaptionOverride, // @ts-ignore + CaptionMessage: CaptionMessageOverride, + // @ts-ignore + CaptionIcon: CaptionIconOverride, + // @ts-ignore ControlContainer: ControlContainerOverride, }, label, @@ -82,9 +111,12 @@ export default class FormControl extends React.Component<FormControlProps, FormC const LabelEndEnhancer = getOverride(LabelEndEnhancerOverride) || StyledLabelEndEnhancer; const LabelContainer = getOverride(LabelContainerOverride) || StyledLabelContainer; const Caption = getOverride(CaptionOverride) || StyledCaption; + const CaptionMessage = getOverride(CaptionMessageOverride) || StyledCaptionMessage; + const CaptionIcon = getOverride(CaptionIconOverride) || StyledCaptionIcon; const ControlContainer = getOverride(ControlContainerOverride) || StyledControlContainer; const hint = chooseRenderedHint(caption, error, positive, sharedProps); + const hintIcon = chooseRenderedHintIcon(hint, error, positive); if (__DEV__) { if (error && positive) { @@ -191,14 +223,15 @@ export default class FormControl extends React.Component<FormControlProps, FormC : sharedProps.$positive, }); })} - {(!!caption || !!error || positive) && ( + {hint && ( <Caption data-baseweb="form-control-caption" id={captionId} {...sharedProps} {...getOverrideProps(CaptionOverride)} > - {hint} + {hintIcon && <CaptionIcon>{hintIcon}</CaptionIcon>} + <CaptionMessage>{hint}</CaptionMessage> </Caption> )} </ControlContainer> diff --git a/src/form-control/styled-components.ts b/src/form-control/styled-components.ts index 519cebb81d..227f422184 100644 --- a/src/form-control/styled-components.ts +++ b/src/form-control/styled-components.ts @@ -79,11 +79,31 @@ export const Caption = styled<'div', StyleProps>('div', (props) => { marginRight: 0, marginBottom: sizing.scale300, marginLeft: 0, + display: 'flex', }; }); Caption.displayName = 'Caption'; +export const CaptionMessage = styled<'div', StyleProps>('div', { + fontFamily: 'inherit', + fontSize: 'inherit', + fontWeight: 'inherit', + lineHeight: 'inherit', + flexGrow: 1, +}); + +CaptionMessage.displayName = 'CaptionMessage'; + +export const CaptionIcon = styled<'div', StyleProps>('div', ({ $theme }) => ({ + display: 'flex', + paddingTop: $theme.sizing.scale100, + paddingRight: $theme.sizing.scale100, + flexShrink: 0, +})); + +CaptionIcon.displayName = 'CaptionIcon'; + export const ControlContainer = styled<'div', StyleProps>('div', (props) => { const { $theme: { sizing }, diff --git a/src/form-control/types.ts b/src/form-control/types.ts index 31905568f0..7caf8c8077 100644 --- a/src/form-control/types.ts +++ b/src/form-control/types.ts @@ -21,6 +21,10 @@ export type FormControlOverrides = { LabelContainer?: Override; /** Customizes the caption element. */ Caption?: Override; + /** Customizes the caption message element. */ + CaptionMessage?: Override; + /** Customizes the caption icon element. */ + CaptionIcon?: Override; /** Customizes the container element. */ ControlContainer?: Override; }; diff --git a/src/icon/circle-check-filled.tsx b/src/icon/circle-check-filled.tsx new file mode 100644 index 0000000000..a6a63e0628 --- /dev/null +++ b/src/icon/circle-check-filled.tsx @@ -0,0 +1,46 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +// BASEUI-GENERATED-REACT-ICON +// DO NOT EDIT THIS FILE DIRECTLY, SEE README.md +import * as React from 'react'; +import { useStyletron } from '../styles'; +import { mergeOverride, toObjectOverride } from '../helpers/overrides'; + +import Icon from './icon'; +import type { IconProps } from './types'; + +// @ts-ignore +function CircleCheckFilled(props: IconProps, ref) { + const [, theme] = useStyletron(); + const { title = 'CircleCheckFilled', size, color, overrides = {}, ...restProps } = props; + const SvgOverride = mergeOverride( + // Icons from the theme target the SVG override in the underlying Icon component + { + component: + theme.icons && theme.icons.CircleCheckFilled ? theme.icons.CircleCheckFilled : null, + }, + overrides && overrides.Svg ? toObjectOverride(overrides.Svg) : {} + ); + return ( + <Icon + viewBox="0 0 24 24" + ref={ref} + title={title} + size={size} + color={color} + overrides={{ Svg: SvgOverride }} + {...restProps} + > + <path + d="M12 1C5.9 1 1 5.9 1 12s4.9 11 11 11 11-4.9 11-11S18.1 1 12 1Zm-1.5 16.1-5.6-5.6L7 9.4l3.4 3.4 6.4-6.4 2.1 2.1-8.4 8.6Z" + fill="currentColor" + /> + </Icon> + ); +} + +export default React.forwardRef<SVGSVGElement, IconProps>(CircleCheckFilled); diff --git a/src/icon/hide.tsx b/src/icon/hide.tsx index a82c9e4dcc..7ecd5f4824 100644 --- a/src/icon/hide.tsx +++ b/src/icon/hide.tsx @@ -26,7 +26,7 @@ function Hide(props: IconProps, ref) { ); return ( <Icon - viewBox="0 0 20 20" + viewBox="0 0 24 24" ref={ref} title={title} size={size} diff --git a/src/icon/icon-exports.ts b/src/icon/icon-exports.ts index e0e463f2c4..2017a78718 100644 --- a/src/icon/icon-exports.ts +++ b/src/icon/icon-exports.ts @@ -14,13 +14,10 @@ export { default as Calendar } from './calendar'; export { default as CheckIndeterminate } from './check-indeterminate'; export { default as Check } from './check'; export { default as ChevronDown } from './chevron-down'; -export { default as ChevronDownSmall } from './chevron-down-small'; export { default as ChevronLeft } from './chevron-left'; -export { default as ChevronLeftSmall } from './chevron-left-small'; export { default as ChevronRight } from './chevron-right'; -export { default as ChevronRightSmall } from './chevron-right-small'; export { default as ChevronUp } from './chevron-up'; -export { default as ChevronUpSmall } from './chevron-up-small'; +export { default as CircleCheckFilled } from './circle-check-filled'; export { default as DeleteAlt } from './delete-alt'; export { default as Delete } from './delete'; export { default as Filter } from './filter'; @@ -28,10 +25,12 @@ export { default as Grab } from './grab'; export { default as Hide } from './hide'; export { default as Menu } from './menu'; export { default as Overflow } from './overflow'; +export { default as PaperclipFilled } from './paperclip-filled'; export { default as Plus } from './plus'; export { default as Search } from './search'; export { default as Show } from './show'; export { default as Spinner } from './spinner'; +export { default as TrashCanFilled } from './trash-can-filled'; export { default as TriangleDown } from './triangle-down'; export { default as TriangleLeft } from './triangle-left'; export { default as TriangleRight } from './triangle-right'; diff --git a/src/icon/paperclip-filled.tsx b/src/icon/paperclip-filled.tsx new file mode 100644 index 0000000000..810a137795 --- /dev/null +++ b/src/icon/paperclip-filled.tsx @@ -0,0 +1,45 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +// BASEUI-GENERATED-REACT-ICON +// DO NOT EDIT THIS FILE DIRECTLY, SEE README.md +import * as React from 'react'; +import { useStyletron } from '../styles'; +import { mergeOverride, toObjectOverride } from '../helpers/overrides'; + +import Icon from './icon'; +import type { IconProps } from './types'; + +// @ts-ignore +function PaperclipFilled(props: IconProps, ref) { + const [, theme] = useStyletron(); + const { title = 'PaperclipFilled', size, color, overrides = {}, ...restProps } = props; + const SvgOverride = mergeOverride( + // Icons from the theme target the SVG override in the underlying Icon component + { + component: theme.icons && theme.icons.PaperclipFilled ? theme.icons.PaperclipFilled : null, + }, + overrides && overrides.Svg ? toObjectOverride(overrides.Svg) : {} + ); + return ( + <Icon + viewBox="0 0 24 24" + ref={ref} + title={title} + size={size} + color={color} + overrides={{ Svg: SvgOverride }} + {...restProps} + > + <path + d="M21.1 2.9c-2.54-2.52-6.66-2.52-9.2 0l-9 9c-2.85 2.87-2.21 6.97 0 9.2A6.492 6.492 0 0 0 7.5 23c.17 0 .33-.01.5-.02v-3.05c-1.06.15-2.19-.17-2.98-.96a3.5 3.5 0 0 1 0-4.94l9-9.01c1.37-1.36 3.6-1.36 4.96-.01 1.36 1.37 1.36 3.6 0 4.96l-6.21 6.21c-.38.38-1.05.38-1.43 0-.39-.39-.4-1.04 0-1.43l5.67-5.6-2.11-2.13-5.67 5.6a4.017 4.017 0 0 0-.02 5.69 4.002 4.002 0 0 0 5.67 0l6.21-6.21a6.515 6.515 0 0 0 .01-9.2Z" + fill="currentColor" + /> + </Icon> + ); +} + +export default React.forwardRef<SVGSVGElement, IconProps>(PaperclipFilled); diff --git a/src/icon/show.tsx b/src/icon/show.tsx index 2dc92ddf7f..df2f517ecc 100644 --- a/src/icon/show.tsx +++ b/src/icon/show.tsx @@ -26,7 +26,7 @@ function Show(props: IconProps, ref) { ); return ( <Icon - viewBox="0 0 20 20" + viewBox="0 0 24 24" ref={ref} title={title} size={size} diff --git a/src/icon/trash-can-filled.tsx b/src/icon/trash-can-filled.tsx new file mode 100644 index 0000000000..b353633073 --- /dev/null +++ b/src/icon/trash-can-filled.tsx @@ -0,0 +1,47 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +// BASEUI-GENERATED-REACT-ICON +// DO NOT EDIT THIS FILE DIRECTLY, SEE README.md +import * as React from 'react'; +import { useStyletron } from '../styles'; +import { mergeOverride, toObjectOverride } from '../helpers/overrides'; + +import Icon from './icon'; +import type { IconProps } from './types'; + +// @ts-ignore +function TrashCanFilled(props: IconProps, ref) { + const [, theme] = useStyletron(); + const { title = 'Trash Can Filled', size, color, overrides = {}, ...restProps } = props; + const SvgOverride = mergeOverride( + // Icons from the theme target the SVG override in the underlying Icon component + { + component: theme.icons && theme.icons.TrashCanFilled ? theme.icons.TrashCanFilled : null, + }, + overrides && overrides.Svg ? toObjectOverride(overrides.Svg) : {} + ); + return ( + <Icon + viewBox="0 0 24 24" + ref={ref} + title={title} + size={size} + color={color} + overrides={{ Svg: SvgOverride }} + {...restProps} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M16 1v2h5v3H3V3h5V1h8ZM5 8h14v15H5V8Z" + fill="currentColor" + /> + </Icon> + ); +} + +export default React.forwardRef<SVGSVGElement, IconProps>(TrashCanFilled); diff --git a/src/locale/en_US.ts b/src/locale/en_US.ts index fa25cfec7c..1416e0e26f 100644 --- a/src/locale/en_US.ts +++ b/src/locale/en_US.ts @@ -10,6 +10,7 @@ import datepicker from '../datepicker/locale'; import datatable from '../data-table/locale'; import buttongroup from '../button-group/locale'; import fileuploader from '../file-uploader/locale'; +import fileuploaderbeta from '../file-uploader-beta/locale'; import menu from '../menu/locale'; import modal from '../modal/locale'; import drawer from '../drawer/locale'; @@ -25,6 +26,7 @@ const en_US: Locale = { datatable, buttongroup, fileuploader, + fileuploaderbeta, menu, modal, drawer, diff --git a/src/locale/es_AR.ts b/src/locale/es_AR.ts index 49f8366d9d..09367a3c81 100644 --- a/src/locale/es_AR.ts +++ b/src/locale/es_AR.ts @@ -103,6 +103,12 @@ const es_AR: Locale = { cancel: 'Cancelar', }, + fileuploaderbeta: { + added: 'Cargando...', + error: 'Error al Subir', + processed: 'Subida exitosa', + }, + menu: { noResultsMsg: 'Sin resultados', parentMenuItemAriaLabel: diff --git a/src/locale/index.tsx b/src/locale/index.tsx index 46d2167077..7022f91038 100644 --- a/src/locale/index.tsx +++ b/src/locale/index.tsx @@ -17,6 +17,7 @@ import type { DatepickerLocale as DatepickerLocaleAlias } from '../datepicker'; import type { DataTableLocale as DataTableLocaleAlias } from '../data-table'; import type { ButtonGroupLocale as ButtonGroupLocaleAlias } from '../button-group'; import type { FileUploaderLocale as FileUploaderLocaleAlias } from '../file-uploader'; +import type { FileUploaderBetaLocale as FileUploaderBetaLocaleAlias } from '../file-uploader-beta'; import type { MenuLocale as MenuLocaleAlias } from '../menu'; import type { ModalLocale as ModalLocaleAlias } from '../modal'; import type { DrawerLocale as DrawerLocaleAlias } from '../drawer'; @@ -39,6 +40,7 @@ const LocaleProvider: React.FC<LocaleProviderProps> = (props) => { return ( // this is poorly documented but specifying true enforces that the object is deeply extended // https://www.npmjs.com/package/just-extend + // @ts-expect-error todo(ts-migration) TS2740 Type '{}' is missing the following properties from type 'Locale': accordion, breadcrumbs, datepicker, datatable, and 8 more. <LocaleContext.Provider value={extend(true, {}, en_US, parentLocale, locale)}> {children} </LocaleContext.Provider> @@ -62,6 +64,8 @@ export type ButtonGroupLocale = ButtonGroupLocaleAlias; /** @deprecated inded use type exported from the component itself */ export type FileUploaderLocale = FileUploaderLocaleAlias; /** @deprecated inded use type exported from the component itself */ +export type FileUploaderBetaLocale = FileUploaderBetaLocaleAlias; +/** @deprecated inded use type exported from the component itself */ export type MenuLocale = MenuLocaleAlias; /** @deprecated inded use type exported from the component itself */ export type ModalLocale = ModalLocaleAlias; diff --git a/src/locale/tr_TR.ts b/src/locale/tr_TR.ts index d76ce1b88b..f68196c9ed 100644 --- a/src/locale/tr_TR.ts +++ b/src/locale/tr_TR.ts @@ -103,6 +103,12 @@ const tr_TR: Locale = { cancel: 'İptal', }, + fileuploaderbeta: { + added: 'Yükleniyor...', + error: 'Yükleme hatası', + processed: 'Yükleme başarılı', + }, + menu: { noResultsMsg: 'Sonuç yok', parentMenuItemAriaLabel: diff --git a/src/locale/types.ts b/src/locale/types.ts index d72fed5c96..1e5c6da357 100644 --- a/src/locale/types.ts +++ b/src/locale/types.ts @@ -10,6 +10,7 @@ import type { DatepickerLocale } from '../datepicker'; import type { DataTableLocale } from '../data-table'; import type { ButtonGroupLocale } from '../button-group'; import type { FileUploaderLocale } from '../file-uploader'; +import type { FileUploaderBetaLocale } from '../file-uploader-beta'; import type { MenuLocale } from '../menu'; import type { ModalLocale } from '../modal'; import type { DrawerLocale } from '../drawer'; @@ -24,6 +25,7 @@ export type Locale = { datatable: DataTableLocale; buttongroup: ButtonGroupLocale; fileuploader: FileUploaderLocale; + fileuploaderbeta: FileUploaderBetaLocale; menu: MenuLocale; modal: ModalLocale; drawer: DrawerLocale; diff --git a/src/message-card/message-card.tsx b/src/message-card/message-card.tsx index 1df66a2563..983d741b0d 100644 --- a/src/message-card/message-card.tsx +++ b/src/message-card/message-card.tsx @@ -18,7 +18,7 @@ import Delete from '../icon/delete'; import { useStyletron } from '../styles/index'; import { ThemeProvider, LightTheme } from '../'; import { getBackgroundColorType } from './utils'; -import { colors } from '../tokens'; +import { primitiveColors as colors } from '../tokens'; import { getOverrides, mergeOverrides } from '../helpers/overrides'; import { IMAGE_LAYOUT, BACKGROUND_COLOR_TYPE, BUTTON_KIND } from './constants'; import type { MessageCardProps } from './types'; diff --git a/src/message-card/utils.ts b/src/message-card/utils.ts index 8eecc5353c..bb6903abed 100644 --- a/src/message-card/utils.ts +++ b/src/message-card/utils.ts @@ -4,7 +4,7 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import { colors } from '../tokens'; +import { primitiveColors as colors, primitiveDarkColors } from '../tokens'; import { BACKGROUND_COLOR_TYPE } from './constants'; const LIGHT_COLORS = new Set([ @@ -12,18 +12,22 @@ const LIGHT_COLORS = new Set([ colors.red100, colors.red200, colors.red300, + colors.red400, colors.green50, colors.green100, colors.green200, colors.green300, + colors.green400, colors.teal50, colors.teal100, colors.teal200, colors.teal300, + colors.teal400, colors.blue50, colors.blue100, colors.blue200, colors.blue300, + colors.blue400, colors.cobalt50, colors.cobalt100, colors.cobalt200, @@ -31,10 +35,12 @@ const LIGHT_COLORS = new Set([ colors.purple100, colors.purple200, colors.purple300, + colors.purple400, colors.magenta50, colors.magenta100, colors.magenta200, colors.magenta300, + colors.magenta400, colors.brown50, colors.brown100, colors.brown200, @@ -59,7 +65,6 @@ const LIGHT_COLORS = new Set([ colors.yellow200, colors.yellow300, colors.yellow400, - colors.yellow500, colors.white, colors.gray50, colors.gray100, @@ -69,52 +74,55 @@ const LIGHT_COLORS = new Set([ ]); const DARK_COLORS = new Set([ - colors.red400, - colors.red500, colors.red600, colors.red700, - colors.green400, - colors.green500, + colors.red800, + colors.red900, colors.green600, colors.green700, - colors.teal400, - colors.teal500, + colors.green800, + colors.green900, colors.teal600, colors.teal700, - colors.blue400, - colors.blue500, + colors.teal800, + colors.teal900, colors.blue600, colors.blue700, + colors.blue800, + colors.blue900, + colors.purple600, + colors.purple700, + colors.purple800, + colors.purple900, colors.cobalt300, colors.cobalt400, colors.cobalt500, colors.cobalt600, colors.cobalt700, - colors.purple400, - colors.purple500, - colors.purple600, - colors.purple700, - colors.magenta400, - colors.magenta500, colors.magenta600, colors.magenta700, + colors.magenta800, + colors.magenta900, colors.brown400, colors.brown500, colors.brown600, colors.brown700, - colors.orange500, colors.orange600, colors.orange700, - colors.lime500, + colors.orange800, + colors.orange900, colors.lime600, colors.lime700, + colors.lime800, + colors.lime900, colors.platinum500, colors.platinum600, colors.platinum700, colors.platinum800, colors.yellow600, colors.yellow700, - colors.gray500, + colors.yellow800, + colors.yellow900, colors.gray600, colors.gray700, colors.gray800, @@ -122,7 +130,21 @@ const DARK_COLORS = new Set([ colors.black, ]); -const POOR_CONTRAST_COLORS = new Set([colors.red300, colors.gray500, colors.yellow600]); +const POOR_CONTRAST_COLORS = new Set([ + colors.gray500, + colors.red500, + colors.orange500, + colors.amber500, + colors.yellow600, + colors.lime500, + colors.green500, + colors.teal500, + colors.blue500, + colors.purple500, + colors.magenta500, +]); + +const DARK_COLOR_TOKENS = new Set([...Object.values(primitiveDarkColors)]); export function getBackgroundColorType(backgroundColor: string) { if (__DEV__ && POOR_CONTRAST_COLORS.has(backgroundColor)) { @@ -130,6 +152,16 @@ export function getBackgroundColorType(backgroundColor: string) { `The provided value for backgroundColor, ${backgroundColor}, is not supported because \ it does not pass accessibility contrast tests for either white or black text.` ); + return null; + } + if ( + __DEV__ && + ![colors.white, colors.black].includes(backgroundColor) && + DARK_COLOR_TOKENS.has(backgroundColor) + ) { + console.warn( + `It looks like you're supplying a primitive color token from the "dark" theme color ramp for MessageCard's backgroundColor. Only color tokens from the "light" theme color ramp are supported. MessageCard uses an overlay for dark mode.` + ); } if (LIGHT_COLORS.has(backgroundColor)) { return BACKGROUND_COLOR_TYPE.light; diff --git a/src/phone-input/base-country-picker.tsx b/src/phone-input/base-country-picker.tsx index b6fc89af5f..4d78cb7599 100644 --- a/src/phone-input/base-country-picker.tsx +++ b/src/phone-input/base-country-picker.tsx @@ -89,7 +89,6 @@ export default function CountryPicker(props: CountrySelectProps) { props: { // https://github.com/uber/baseweb/issues/3846 autoComplete: 'chrome-off', - 'aria-label': 'Select country', }, }, IconsContainer: { @@ -231,6 +230,7 @@ export default function CountryPicker(props: CountrySelectProps) { }} options={options} positive={positive} + aria-label="Select country" required={required} size={size} value={[country]} diff --git a/src/phone-input/country-picker.tsx b/src/phone-input/country-picker.tsx index e579a4bbb9..c7b7ff6e5a 100644 --- a/src/phone-input/country-picker.tsx +++ b/src/phone-input/country-picker.tsx @@ -60,16 +60,6 @@ export default function CountryPicker(props: CountrySelectProps) { // @ts-ignore [padEndDir]: sizeToRightPadding[props.$size || SIZE.default], }; - // do not add positive and error color borders when not focused - if (!props.$isFocused && !props.$isPseudoFocused) { - return { - ...styleOverride, - borderLeftColor: 'transparent', - borderRightColor: 'transparent', - borderTopColor: 'transparent', - borderBottomColor: 'transparent', - }; - } return styleOverride; }, }, diff --git a/src/rating/styled-components.ts b/src/rating/styled-components.ts index 0efddb1394..dabd2fc995 100644 --- a/src/rating/styled-components.ts +++ b/src/rating/styled-components.ts @@ -108,18 +108,20 @@ StyledStar.displayName = 'StyledStar'; export const StyledEmoticon = styled<'li', StyledRatingItemProps>( 'li', ({ $theme, $isActive, $isSelected, $index = 1, $isFocusVisible, $isReadOnly, $size }) => { - let emoticonFill = $theme.colors.ratingInactiveFill; + let emoticonFill = $theme.colors.backgroundSecondary; if ($isActive) { emoticonFill = $theme.colors.backgroundWarning; } + const faceColor = $theme.colors.contentPrimary; + const ratingIcons = [ - angryRatingSVG(emoticonFill, $size), - sadRatingSVG(emoticonFill, $size), - neutralRatingSVG(emoticonFill, $size), - happyRatingSVG(emoticonFill, $size), - veryHappyRatingSVG(emoticonFill, $size), + angryRatingSVG(emoticonFill, faceColor, $size), + sadRatingSVG(emoticonFill, faceColor, $size), + neutralRatingSVG(emoticonFill, faceColor, $size), + happyRatingSVG(emoticonFill, faceColor, $size), + veryHappyRatingSVG(emoticonFill, faceColor, $size), ]; const styles: StyleObject = { diff --git a/src/rating/svg-icons.ts b/src/rating/svg-icons.ts index 9a88cb0ce1..f0a0852c34 100644 --- a/src/rating/svg-icons.ts +++ b/src/rating/svg-icons.ts @@ -16,57 +16,57 @@ export function starSVG(fillColor: string, strokeColor: string, size: number) { `); } -export function angryRatingSVG(fillColor: string, size: number) { +export function angryRatingSVG(fillColor: string, faceColor: string, size: number) { return encodeURIComponent(` <svg width="${size}" height="${size}" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="22" cy="22" r="20" fill="${fillColor}"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 29C24.8073 29 26.6585 30.7697 27.1609 31.5442C27.4615 32.0075 28.0807 32.1395 28.5441 31.839C29.0074 31.5384 29.1394 30.9192 28.8388 30.4558C28.0439 29.2303 25.614 27 21.8377 27C18.0453 27 15.8091 29.25 15.1202 30.5245C14.8576 31.0103 15.0385 31.6171 15.5244 31.8797C16.0102 32.1423 16.617 31.9614 16.8796 31.4755C17.2718 30.75 18.8842 29 21.8377 29Z" fill="black"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M11.684 16.949C11.1601 16.7743 10.8769 16.208 11.0516 15.684C11.2262 15.1601 11.7926 14.8769 12.3165 15.0516L18.3165 17.0516C18.8404 17.2262 19.1236 17.7926 18.949 18.3165C18.8093 18.7355 18.4192 19.0005 18.0005 19.0005C18.0004 20.105 17.105 21.0002 16.0005 21.0002C14.896 21.0002 14.0005 20.1048 14.0005 19.0002C14.0005 18.5694 14.1368 18.1703 14.3686 17.8438L11.684 16.949ZM32.949 15.684C33.1237 16.208 32.8405 16.7743 32.3166 16.949L29.6324 17.8437C29.8642 18.1702 30.0005 18.5693 30.0005 19.0002C30.0005 20.1048 29.1051 21.0002 28.0005 21.0002C26.8961 21.0002 26.0007 20.105 26.0005 19.0005C25.5817 19.0007 25.1914 18.7356 25.0516 18.3165C24.877 17.7926 25.1602 17.2262 25.6841 17.0516L31.6841 15.0516C32.208 14.8769 32.7744 15.1601 32.949 15.684Z" fill="black"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 29C24.8073 29 26.6585 30.7697 27.1609 31.5442C27.4615 32.0075 28.0807 32.1395 28.5441 31.839C29.0074 31.5384 29.1394 30.9192 28.8388 30.4558C28.0439 29.2303 25.614 27 21.8377 27C18.0453 27 15.8091 29.25 15.1202 30.5245C14.8576 31.0103 15.0385 31.6171 15.5244 31.8797C16.0102 32.1423 16.617 31.9614 16.8796 31.4755C17.2718 30.75 18.8842 29 21.8377 29Z" fill="${faceColor}"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M11.684 16.949C11.1601 16.7743 10.8769 16.208 11.0516 15.684C11.2262 15.1601 11.7926 14.8769 12.3165 15.0516L18.3165 17.0516C18.8404 17.2262 19.1236 17.7926 18.949 18.3165C18.8093 18.7355 18.4192 19.0005 18.0005 19.0005C18.0004 20.105 17.105 21.0002 16.0005 21.0002C14.896 21.0002 14.0005 20.1048 14.0005 19.0002C14.0005 18.5694 14.1368 18.1703 14.3686 17.8438L11.684 16.949ZM32.949 15.684C33.1237 16.208 32.8405 16.7743 32.3166 16.949L29.6324 17.8437C29.8642 18.1702 30.0005 18.5693 30.0005 19.0002C30.0005 20.1048 29.1051 21.0002 28.0005 21.0002C26.8961 21.0002 26.0007 20.105 26.0005 19.0005C25.5817 19.0007 25.1914 18.7356 25.0516 18.3165C24.877 17.7926 25.1602 17.2262 25.6841 17.0516L31.6841 15.0516C32.208 14.8769 32.7744 15.1601 32.949 15.684Z" fill="${faceColor}"/> </svg> `); } -export function sadRatingSVG(fillColor: string, size: number) { +export function sadRatingSVG(fillColor: string, faceColor: string, size: number) { return encodeURIComponent(` <svg width="${size}" height="${size}" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="22" cy="22" r="20" fill="${fillColor}"/> - <ellipse cx="16" cy="18" rx="2" ry="2" fill="black"/> - <ellipse cx="28" cy="18" rx="2" ry="2" fill="black"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 28C24.8073 28 26.6585 29.7697 27.1609 30.5442C27.4615 31.0075 28.0807 31.1395 28.5441 30.839C29.0074 30.5384 29.1394 29.9192 28.8388 29.4558C28.0439 28.2303 25.614 26 21.8377 26C18.0453 26 15.8091 28.25 15.1202 29.5245C14.8576 30.0103 15.0385 30.6171 15.5244 30.8797C16.0102 31.1423 16.617 30.9614 16.8796 30.4755C17.2718 29.75 18.8842 28 21.8377 28Z" fill="black"/> + <ellipse cx="16" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <ellipse cx="28" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 28C24.8073 28 26.6585 29.7697 27.1609 30.5442C27.4615 31.0075 28.0807 31.1395 28.5441 30.839C29.0074 30.5384 29.1394 29.9192 28.8388 29.4558C28.0439 28.2303 25.614 26 21.8377 26C18.0453 26 15.8091 28.25 15.1202 29.5245C14.8576 30.0103 15.0385 30.6171 15.5244 30.8797C16.0102 31.1423 16.617 30.9614 16.8796 30.4755C17.2718 29.75 18.8842 28 21.8377 28Z" fill="${faceColor}"/> </svg> `); } -export function neutralRatingSVG(fillColor: string, size: number) { +export function neutralRatingSVG(fillColor: string, faceColor: string, size: number) { return encodeURIComponent(` <svg width="${size}" height="${size}" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="22" cy="22" r="20" fill="${fillColor}"/> - <ellipse cx="16" cy="19" rx="2" ry="2" fill="black"/> - <ellipse cx="28" cy="19" rx="2" ry="2" fill="black"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M15 28C15 27.4477 15.4477 27 16 27L16 28L16 29C15.4477 29 15 28.5523 15 28ZM28 28L28 29H16L16 28L16 27H27.9995L28 28ZM28 28L28 29C28.5523 29 29 28.5523 29 28C29 27.4477 28.5518 27 27.9995 27L28 28Z" fill="black"/> + <ellipse cx="16" cy="19" rx="2" ry="2" fill="${faceColor}"/> + <ellipse cx="28" cy="19" rx="2" ry="2" fill="${faceColor}"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M15 28C15 27.4477 15.4477 27 16 27L16 28L16 29C15.4477 29 15 28.5523 15 28ZM28 28L28 29H16L16 28L16 27H27.9995L28 28ZM28 28L28 29C28.5523 29 29 28.5523 29 28C29 27.4477 28.5518 27 27.9995 27L28 28Z" fill="${faceColor}"/> </svg> `); } -export function happyRatingSVG(fillColor: string, size: number) { +export function happyRatingSVG(fillColor: string, faceColor: string, size: number) { return encodeURIComponent(` <svg width="${size}" height="${size}" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="22" cy="22" r="20" fill="${fillColor}"/> - <ellipse cx="16" cy="18" rx="2" ry="2" fill="black"/> - <ellipse cx="28" cy="18" rx="2" ry="2" fill="black"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 29C24.8073 29 26.6585 27.2303 27.1609 26.4558C27.4615 25.9925 28.0807 25.8605 28.5441 26.161C29.0074 26.4616 29.1394 27.0808 28.8388 27.5442C28.0439 28.7697 25.614 31 21.8377 31C18.0453 31 15.8091 28.75 15.1202 27.4755C14.8576 26.9897 15.0385 26.3829 15.5244 26.1203C16.0102 25.8577 16.617 26.0386 16.8796 26.5245C17.2718 27.25 18.8842 29 21.8377 29Z" fill="black"/> + <ellipse cx="16" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <ellipse cx="28" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M21.8377 29C24.8073 29 26.6585 27.2303 27.1609 26.4558C27.4615 25.9925 28.0807 25.8605 28.5441 26.161C29.0074 26.4616 29.1394 27.0808 28.8388 27.5442C28.0439 28.7697 25.614 31 21.8377 31C18.0453 31 15.8091 28.75 15.1202 27.4755C14.8576 26.9897 15.0385 26.3829 15.5244 26.1203C16.0102 25.8577 16.617 26.0386 16.8796 26.5245C17.2718 27.25 18.8842 29 21.8377 29Z" fill="${faceColor}"/> </svg> `); } -export function veryHappyRatingSVG(fillColor: string, size: number) { +export function veryHappyRatingSVG(fillColor: string, faceColor: string, size: number) { return encodeURIComponent(` <svg width="${size}" height="${size}" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="22" cy="22" r="20" fill="${fillColor}"/> - <ellipse cx="16" cy="18" rx="2" ry="2" fill="black"/> - <ellipse cx="28" cy="18" rx="2" ry="2" fill="black"/> - <path d="M21.8378 31C26.7027 31 28 27.8 28 27H16C16 27.8 16.973 31 21.8378 31Z" fill="black"/> - <path fill-rule="evenodd" clip-rule="evenodd" d="M26.5528 28H17.3513C17.4656 28.1996 17.6126 28.3984 17.8211 28.6128C18.4608 29.2891 19.6453 30 21.8378 30C24.0315 30 25.3168 29.2869 26.0488 28.5805C26.2631 28.3781 26.4239 28.1881 26.5528 28ZM27.4377 30.0195C26.3048 31.1131 24.5091 32 21.8378 32C19.1655 32 17.4311 31.1109 16.3681 29.9872C15.3474 28.9082 15 27.6646 15 27C15 26.4477 15.4477 26 16 26H28C28.5523 26 29 26.4477 29 27C29 27.404 28.8568 27.9069 28.6231 28.3946C28.3936 28.868 27.9783 29.4894 27.4377 30.0195Z" fill="black"/> + <ellipse cx="16" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <ellipse cx="28" cy="18" rx="2" ry="2" fill="${faceColor}"/> + <path d="M21.8378 31C26.7027 31 28 27.8 28 27H16C16 27.8 16.973 31 21.8378 31Z" fill="${faceColor}"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M26.5528 28H17.3513C17.4656 28.1996 17.6126 28.3984 17.8211 28.6128C18.4608 29.2891 19.6453 30 21.8378 30C24.0315 30 25.3168 29.2869 26.0488 28.5805C26.2631 28.3781 26.4239 28.1881 26.5528 28ZM27.4377 30.0195C26.3048 31.1131 24.5091 32 21.8378 32C19.1655 32 17.4311 31.1109 16.3681 29.9872C15.3474 28.9082 15 27.6646 15 27C15 26.4477 15.4477 26 16 26H28C28.5523 26 29 26.4477 29 27C29 27.404 28.8568 27.9069 28.6231 28.3946C28.3936 28.868 27.9783 29.4894 27.4377 30.0195Z" fill="${faceColor}"/> </svg> `); } diff --git a/src/select/multi-value.tsx b/src/select/multi-value.tsx index 3be83b3dd8..d23c5dd0e9 100644 --- a/src/select/multi-value.tsx +++ b/src/select/multi-value.tsx @@ -6,7 +6,7 @@ LICENSE file in the root directory of this source tree. */ import * as React from 'react'; import { getOverrides } from '../helpers/overrides'; -import { Tag, VARIANT as TAG_VARIANT } from '../tag'; +import { Tag, HIERARCHY } from '../tag'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export default function MultiValue(props: any) { @@ -15,7 +15,7 @@ export default function MultiValue(props: any) { const [MultiValue, tagProps] = getOverrides(overrides.Tag || overrides.MultiValue, Tag); return ( <MultiValue - variant={TAG_VARIANT.solid} + hierarchy={HIERARCHY.primary} overrides={{ Root: { // @ts-ignore diff --git a/src/snackbar/__tests__/use-snackbar.test.tsx b/src/snackbar/__tests__/use-snackbar.test.tsx new file mode 100644 index 0000000000..3e703d8ce1 --- /dev/null +++ b/src/snackbar/__tests__/use-snackbar.test.tsx @@ -0,0 +1,40 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ + +import React, { useEffect, useState, useRef } from 'react'; +import { render, screen } from '@testing-library/react'; + +import { useSnackbar } from '..'; + +// https://usehooks.com/usePrevious/ +export function usePrevious(value: any) { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; +} + +describe('snackbar-element', () => { + test('maintain reference of context after re-render', () => { + const Component = () => { + const context = useSnackbar(); + const [count, setCount] = useState(0); + useEffect(() => { + setCount((c) => ++c); + }, [context]); + return ( + <div> + <div>{count}</div> + </div> + ); + }; + render(<Component />); + + screen.getByText('1'); + }); +}); diff --git a/src/snackbar/index.ts b/src/snackbar/index.ts index d6e5555fe6..750ea03462 100644 --- a/src/snackbar/index.ts +++ b/src/snackbar/index.ts @@ -16,6 +16,7 @@ import type { export { DURATION, PLACEMENT } from './constants'; export { default as SnackbarElement } from './snackbar-element'; export { default as SnackbarProvider, useSnackbar } from './snackbar-context'; +export type { Context as SnackbarContextT } from './snackbar-context'; export { StyledRoot, StyledContent, diff --git a/src/snackbar/snackbar-context.tsx b/src/snackbar/snackbar-context.tsx index 20c66cdb87..d06ee21ae0 100644 --- a/src/snackbar/snackbar-context.tsx +++ b/src/snackbar/snackbar-context.tsx @@ -16,7 +16,7 @@ import SnackbarElement from './snackbar-element'; import { StyledPlacementContainer } from './styled-components'; import type { SnackbarElementProps, SnackbarProviderProps, Duration } from './types'; -type Context = { +export type Context = { enqueue: (elementProps: SnackbarElementProps, duration?: Duration) => void; dequeue: () => void; }; @@ -34,8 +34,14 @@ export const SnackbarContext: React.Context<Context> = React.createContext({ }); export function useSnackbar() { - const context = React.useContext(SnackbarContext); - return { enqueue: context.enqueue, dequeue: context.dequeue }; + const { enqueue, dequeue } = React.useContext(SnackbarContext); + /* We use an empty dependency array because `enquque` and `dequeue` never change. + Ideally we'd memoize these functions and include them in the dependency array, + but that would require us to memoize many more functions in the SnackbarProvider, + and those functions depend on eachother in a circular way. + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + return React.useMemo(() => ({ enqueue, dequeue }), []); } // @ts-ignore @@ -80,19 +86,6 @@ export default function SnackbarProvider({ } }, [snackbars, prevSnackbars]); - function dequeue() { - setContainerHeight(0); - - setSnackbars((prev) => { - const next = prev.slice(1); - if (next.length > 0) { - // @ts-ignore - enter(next[0].duration); - } - return next; - }); - } - // @ts-ignore function enter(duration) { setAnimating(true); @@ -102,11 +95,19 @@ export default function SnackbarProvider({ }, 0); } - function exit() { + function dequeue() { setAnimating(true); setTimeout(() => { setAnimating(false); - dequeue(); + setContainerHeight(0); + setSnackbars((prev) => { + const next = prev.slice(1); + if (next.length > 0) { + // @ts-ignore + enter(next[0].duration); + } + return next; + }); }, 1000); } @@ -118,7 +119,7 @@ export default function SnackbarProvider({ // @ts-ignore timeoutRef.current = setTimeout(() => { - exit(); + dequeue(); }, duration); } @@ -135,7 +136,7 @@ export default function SnackbarProvider({ function handleActionClick() { // @ts-ignore clearTimeout(timeoutRef.current); - exit(); + dequeue(); } React.useEffect(() => { @@ -172,7 +173,7 @@ export default function SnackbarProvider({ ); return ( - <SnackbarContext.Provider value={{ enqueue, dequeue: exit }}> + <SnackbarContext.Provider value={{ enqueue, dequeue }}> <div className={css({ boxSizing: 'border-box', diff --git a/src/spinner/index.ts b/src/spinner/index.ts deleted file mode 100644 index 84cf7fdf94..0000000000 --- a/src/spinner/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -import { StyledSpinner } from './styled-components'; - -export { StyledSpinner as Spinner }; -export { SIZE } from './constants'; -// Flow -export * from './types'; diff --git a/src/stepper/stepper.tsx b/src/stepper/stepper.tsx index c56d235836..267b7bd0f5 100644 --- a/src/stepper/stepper.tsx +++ b/src/stepper/stepper.tsx @@ -4,7 +4,7 @@ This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ import * as React from 'react'; -import { getOverrides } from '../helpers/overrides'; +import { getOverrides, mergeOverrides } from '../helpers/overrides'; import { Button, KIND, SHAPE, SIZE } from '../button'; import { Input as DefaultInput } from '../input'; import { Plus, CheckIndeterminate } from '../icon'; @@ -39,14 +39,41 @@ export function Stepper({ CheckIndeterminate ); const [IncrementButton, incrementButtonProps] = getOverrides( - overrides.DecrementButton, + overrides.IncrementButton, DefaultButton ); const [IncrementButtonIcon, incrementButtonIconProps] = getOverrides( - overrides.DecrementButtonIcon, + overrides.IncrementButtonIcon, Plus ); + const [, theme] = useStyletron(); + + const defaultInputOverrides = { + Root: { + style: { + maxWidth: '36px', + height: '36px', + borderLeftStyle: 'none', + borderRightStyle: 'none', + borderTopStyle: 'none', + borderBottomStyle: 'none', + }, + }, + Input: { + style: { + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, + paddingRight: 0, + textAlign: 'center', + backgroundColor: theme.colors.backgroundPrimary, + ...theme.typography.LabelLarge, + }, + }, + }; + inputProps.overrides = mergeOverrides(defaultInputOverrides, inputProps.overrides); + const handleInputChange = (e) => { const newValue = Number(e.target.value); if (!isNaN(newValue) && (!maxValue || newValue <= maxValue) && newValue >= minValue) { @@ -54,8 +81,6 @@ export function Stepper({ } }; - const [, theme] = useStyletron(); - return ( <Root {...rootProps}> <DecrementButton @@ -71,29 +96,6 @@ export function Stepper({ onChange={handleInputChange} disabled={disabled} aria-label="value" - overrides={{ - Root: { - style: { - maxWidth: '36px', - height: '36px', - borderLeftStyle: 'none', - borderRightStyle: 'none', - borderTopStyle: 'none', - borderBottomStyle: 'none', - }, - }, - Input: { - style: { - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 0, - paddingRight: 0, - textAlign: 'center', - backgroundColor: theme.colors.backgroundPrimary, - ...theme.typography.LabelLarge, - }, - }, - }} {...inputProps} /> <IncrementButton diff --git a/src/styles/__mocks__/styled.tsx b/src/styles/__mocks__/styled.tsx index 550f05bd1d..f161f99808 100644 --- a/src/styles/__mocks__/styled.tsx +++ b/src/styles/__mocks__/styled.tsx @@ -26,7 +26,6 @@ type State = { }; const MOCK_THEME = createMockTheme(LightTheme); -// @ts-ignore const IDENTITY = (x) => x; export function useStyletron() { @@ -74,7 +73,6 @@ export function styled(ElementName: string | React.ComponentType<any>, objOrFn: const { forwardedRef, ...restProps } = this.props; return Object.keys(restProps).reduce((acc, key) => { if (key[0] !== '$') { - // @ts-ignore acc[key] = restProps[key]; } return acc; diff --git a/src/styles/__tests__/styled.test.tsx b/src/styles/__tests__/styled.test.tsx index 3c5101a55b..b3b053fa90 100644 --- a/src/styles/__tests__/styled.test.tsx +++ b/src/styles/__tests__/styled.test.tsx @@ -63,12 +63,10 @@ test('styled can be called with single string argument', () => { }); test('styled override prop', () => { - // @ts-ignore const StyledMockButton = styled<'button', { $color? }>('button', { color: 'red', }); - // @ts-ignore const styleFn = (props) => { return { color: props.$color }; }; @@ -152,7 +150,6 @@ describe('styled flow', () => { }); test('it provides expected flow error if base is react component', () => { - // @ts-ignore function C(props) { return <div className={props.className}>test</div>; } diff --git a/src/styles/as-primary-export-hoc.tsx b/src/styles/as-primary-export-hoc.tsx index 4a509e976d..f52401e878 100644 --- a/src/styles/as-primary-export-hoc.tsx +++ b/src/styles/as-primary-export-hoc.tsx @@ -14,10 +14,8 @@ export default function asPrimaryExport( return function withStyledPropsHOC(props: {}) { const styledProps = Object.keys(props).reduce((acc, key) => { if (key[0] === '$' || propsTransformNames.indexOf(key) < 0) { - // @ts-ignore acc[key] = props[key]; } else if (propsTransformNames.indexOf(key) >= 0) { - // @ts-ignore acc['$' + key] = props[key]; } return acc; diff --git a/src/styles/styled.tsx b/src/styles/styled.tsx index 8996b877a7..a6452ea562 100644 --- a/src/styles/styled.tsx +++ b/src/styles/styled.tsx @@ -18,7 +18,6 @@ import type { Theme } from './types'; import { ThemeContext } from './theme-provider'; -// @ts-ignore const wrapper = (StyledComponent) => { // eslint-disable-next-line react/display-name return React.forwardRef((props, ref) => ( @@ -108,7 +107,6 @@ export function withWrapper<C extends StyletronComponent<any, any>, Props>( props: Props & (C extends StyletronComponent<any, infer CP> ? CP : never) ) => React.ReactElement ): C extends StyletronComponent<infer D, infer P> ? StyletronComponent<D, P & Props> : never { - // @ts-ignore return styletronWithWrapper(StyledElement, (Styled) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any,react/display-name return React.forwardRef<any, React.ComponentProps<C> & Props>((props, ref) => ( diff --git a/src/styles/types.ts b/src/styles/types.ts index 04dc988a3a..2793a5ba35 100644 --- a/src/styles/types.ts +++ b/src/styles/types.ts @@ -8,9 +8,9 @@ import type { ComponentType } from 'react'; import type { IconProps } from '../icon'; import type { - FoundationColorTokens, - ComponentColorTokens, - SemanticColorTokens, + FoundationColors, + ComponentColors, + SemanticColors, Animation, Breakpoints, Border, @@ -23,7 +23,7 @@ import type { Typography, ZIndex, } from '../themes'; -import type { PrimitiveColorTokens } from '../tokens'; +import type { PrimitiveColors } from '../tokens'; export type { Animation, @@ -39,10 +39,10 @@ export type { ZIndex, }; -export type ColorTokens = PrimitiveColorTokens & - FoundationColorTokens & - ComponentColorTokens & - SemanticColorTokens & { [key in string]: string }; +export type ColorTokens = PrimitiveColors & + FoundationColors & + ComponentColors & + SemanticColors & { [key in string]: string }; export type Theme = { name: string; @@ -82,6 +82,7 @@ export type Icon = { ChevronLeftSmall?: ComponentType<IconProps> | ForwardedSVG; ChevronRight?: ComponentType<IconProps> | ForwardedSVG; ChevronRightSmall?: ComponentType<IconProps> | ForwardedSVG; + CircleCheckFilled?: ComponentType<IconProps> | ForwardedSVG; DeleteAlt?: ComponentType<IconProps> | ForwardedSVG; Delete?: ComponentType<IconProps> | ForwardedSVG; Filter?: ComponentType<IconProps> | ForwardedSVG; @@ -89,10 +90,12 @@ export type Icon = { Hide?: ComponentType<IconProps> | ForwardedSVG; Menu?: ComponentType<IconProps> | ForwardedSVG; Overflow?: ComponentType<IconProps> | ForwardedSVG; + PaperclipFilled?: ComponentType<IconProps> | ForwardedSVG; Plus?: ComponentType<IconProps> | ForwardedSVG; Search?: ComponentType<IconProps> | ForwardedSVG; Show?: ComponentType<IconProps> | ForwardedSVG; Spinner?: ComponentType<IconProps> | ForwardedSVG; + TrashCanFilled?: ComponentType<IconProps> | ForwardedSVG; TriangleDown?: ComponentType<IconProps> | ForwardedSVG; TriangleLeft?: ComponentType<IconProps> | ForwardedSVG; TriangleRight?: ComponentType<IconProps> | ForwardedSVG; diff --git a/src/tabs-motion/tabs.tsx b/src/tabs-motion/tabs.tsx index d47af52fa9..4bb7c7fbf0 100644 --- a/src/tabs-motion/tabs.tsx +++ b/src/tabs-motion/tabs.tsx @@ -162,8 +162,8 @@ export function Tabs({ } }, [activeTabRef.current, orientation]); - // Update highlight on key and orientation changes. - React.useEffect(updateHighlight, [activeTabRef.current, orientation]); + // Update highlight on key, orientation and children changes. + React.useEffect(updateHighlight, [activeTabRef.current, orientation, children]); // Scroll active tab into view when the parent has scrollbar on mount and // on key change (smooth scroll). Note, if the active key changes while diff --git a/src/tag/__tests__/tag-start-enhancer.scenario.tsx b/src/tag/__tests__/tag-start-enhancer.scenario.tsx index 124ed048ed..958545dffc 100644 --- a/src/tag/__tests__/tag-start-enhancer.scenario.tsx +++ b/src/tag/__tests__/tag-start-enhancer.scenario.tsx @@ -8,7 +8,7 @@ LICENSE file in the root directory of this source tree. import * as React from 'react'; import { Upload } from '../../icon'; import { useStyletron } from '../../styles'; -import { Tag, KIND, SIZE, VARIANT } from '..'; +import { Tag, KIND, SIZE, HIERARCHY } from '..'; const customColor = '#26c6da'; @@ -48,7 +48,7 @@ export function Scenario() { kind={kind} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -68,7 +68,7 @@ export function Scenario() { <Tag startEnhancer={() => <Upload />} kind={kind} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > @@ -114,7 +114,7 @@ export function Scenario() { kind={kind} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -134,7 +134,7 @@ export function Scenario() { <Tag startEnhancer={() => <Upload />} kind={kind} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > @@ -171,7 +171,7 @@ export function Scenario() { color={customColor} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -199,7 +199,7 @@ export function Scenario() { startEnhancer={() => <Upload />} kind={KIND.custom} color={customColor} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > diff --git a/src/tag/__tests__/tag.scenario.tsx b/src/tag/__tests__/tag.scenario.tsx index f1c0125a72..4d950f3772 100644 --- a/src/tag/__tests__/tag.scenario.tsx +++ b/src/tag/__tests__/tag.scenario.tsx @@ -7,7 +7,7 @@ LICENSE file in the root directory of this source tree. /* global alert */ import * as React from 'react'; import { useStyletron } from '../../styles'; -import { Tag, KIND, VARIANT } from '..'; +import { Tag, KIND, HIERARCHY } from '..'; const customColor = '#26c6da'; @@ -28,7 +28,7 @@ export function Scenario() { kind={kind} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -42,7 +42,7 @@ export function Scenario() { </Tag> <Tag kind={kind} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > @@ -77,7 +77,7 @@ export function Scenario() { kind={kind} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -91,7 +91,7 @@ export function Scenario() { </Tag> <Tag kind={kind} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > @@ -121,7 +121,7 @@ export function Scenario() { color={customColor} onClick={() => alert('click')} closeable={false} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} > Label </Tag> @@ -141,7 +141,7 @@ export function Scenario() { <Tag kind={KIND.custom} color={customColor} - variant={VARIANT.solid} + hierarchy={HIERARCHY.primary} onClick={() => alert('click')} onActionClick={() => alert('action')} > diff --git a/src/tag/constants.ts b/src/tag/constants.ts index f17955d5a4..358bb1acc2 100644 --- a/src/tag/constants.ts +++ b/src/tag/constants.ts @@ -10,10 +10,9 @@ export const SIZE = { large: 'large', } as const; -export const VARIANT = Object.freeze({ - solid: 'solid', - light: 'light', - outlined: 'outlined', +export const HIERARCHY = Object.freeze({ + primary: 'primary', + secondary: 'secondary', } as const); // todo: dynamic identity map generation @@ -35,4 +34,6 @@ export const KIND = { orange: 'orange', purple: 'purple', brown: 'brown', + teal: 'teal', + lime: 'lime', } as const; diff --git a/src/tag/index.ts b/src/tag/index.ts index 18deb5135d..32ceb0484f 100644 --- a/src/tag/index.ts +++ b/src/tag/index.ts @@ -11,5 +11,5 @@ export { Action as StyledAction, Text as StyledText, } from './styled-components'; -export { KIND, VARIANT, SIZE } from './constants'; +export { KIND, HIERARCHY, SIZE } from './constants'; export * from './types'; diff --git a/src/tag/styled-components.ts b/src/tag/styled-components.ts index 16c86cc832..98721d6ef4 100644 --- a/src/tag/styled-components.ts +++ b/src/tag/styled-components.ts @@ -8,38 +8,35 @@ import tint from 'polished/lib/color/tint.js'; import shade from 'polished/lib/color/shade.js'; import { styled, type Theme } from '../styles'; -import { KIND, VARIANT, SIZE } from './constants'; +import { KIND, HIERARCHY, SIZE } from './constants'; import type { SharedPropsArg } from './types'; -export function customOnRamp(color?: string, unit?: string) { +export function customOnRamp(color: string, unit?: string) { + // This is a temporary fix to prevent the tag from crashing when the color is not defined + if (!color && !(unit === '0' || unit === '1000')) { + return undefined; + } + switch (unit) { case '0': return 'white'; case '50': - // @ts-ignore return tint(0.8, color); case '100': - // @ts-ignore return tint(0.6, color); case '200': - // @ts-ignore return tint(0.4, color); case '300': - // @ts-ignore return tint(0.2, color); case '400': return color; case '500': - // @ts-ignore return shade(0.2, color); case '600': - // @ts-ignore return shade(0.4, color); case '700': - // @ts-ignore return shade(0.6, color); case '800': - // @ts-ignore return shade(0.8, color); case '1000': return 'black'; @@ -50,8 +47,8 @@ export function customOnRamp(color?: string, unit?: string) { const COLOR_STATE = { disabled: 'disabled', - solid: 'solid', - outline: 'outline', + primary: 'primary', + secondary: 'secondary', } as const; // Probably best to bake this into the theme once we hit our next major. @@ -63,25 +60,22 @@ const neutralColorStates = { // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagNeutralFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagNeutralOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.gray50, theme.colors.gray100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagNeutralSolidFont, backgroundColor: theme.colors.tagNeutralSolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagNeutralOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagNeutralOutlinedBackground, + backgroundColor: theme.colors.tagNeutralOutlinedBackground, + borderColor: null, }), }; @@ -90,133 +84,166 @@ const primaryColorStates = { // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagPrimaryFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagPrimaryOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.gray50, theme.colors.gray100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagPrimarySolidFont, backgroundColor: theme.colors.tagPrimarySolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagPrimaryOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagPrimaryOutlinedBackground, + backgroundColor: theme.colors.tagPrimaryOutlinedBackground, + borderColor: null, }), }; -const accentColorStates = { +const blueColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagAccentFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagAccentOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.blue50, theme.colors.blue100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagAccentSolidFont, backgroundColor: theme.colors.tagAccentSolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagAccentOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagAccentOutlinedBackground, + backgroundColor: theme.colors.tagAccentOutlinedBackground, + borderColor: null, }), }; -const positiveColorStates = { +const greenColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagPositiveFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagPositiveOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.green50, theme.colors.green100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagPositiveSolidFont, backgroundColor: theme.colors.tagPositiveSolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagPositiveOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagPositiveOutlinedBackground, + backgroundColor: theme.colors.tagPositiveOutlinedBackground, + borderColor: null, }), }; -const warningColorStates = { +const yellowColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagWarningFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagWarningOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.yellow50, theme.colors.yellow100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagWarningSolidFont, backgroundColor: theme.colors.tagWarningSolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagWarningOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagWarningOutlinedBackground, + backgroundColor: theme.colors.tagWarningOutlinedBackground, + borderColor: null, }), }; -const negativeColorStates = { +const redColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: theme.colors.tagNegativeFontDisabled, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagNegativeOutlinedDisabled, + backgroundColor: pick(theme, theme.colors.red50, theme.colors.red100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: theme.colors.tagNegativeSolidFont, backgroundColor: theme.colors.tagNegativeSolidBackground, - // @ts-ignore borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: theme.colors.tagNegativeOutlinedFont, - // @ts-ignore - backgroundColor: null, - borderColor: theme.colors.tagNegativeOutlinedBackground, + backgroundColor: theme.colors.tagNegativeOutlinedBackground, + borderColor: null, + }), +}; + +const limeColorStates = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.disabled]: (theme, color) => ({ + color: pick(theme, theme.colors.lime300, theme.colors.lime400Dark), + backgroundColor: pick(theme, theme.colors.lime50, theme.colors.lime100Dark), + borderColor: null, + }), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.primary]: (theme, color) => ({ + color: pick(theme, theme.colors.white, theme.colors.lime900Dark), + backgroundColor: pick(theme, theme.colors.lime600, theme.colors.lime400Dark), + borderColor: null, + }), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.secondary]: (theme, color) => ({ + color: pick(theme, theme.colors.lime700, theme.colors.lime700Dark), + backgroundColor: pick(theme, theme.colors.lime50, theme.colors.lime100Dark), + borderColor: null, + }), +}; + +const tealColorStates = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.disabled]: (theme, color) => ({ + color: pick(theme, theme.colors.teal300, theme.colors.teal400Dark), + backgroundColor: pick(theme, theme.colors.teal50, theme.colors.teal100Dark), + borderColor: null, + }), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.primary]: (theme, color) => ({ + color: pick(theme, theme.colors.white, theme.colors.teal900Dark), + backgroundColor: pick(theme, theme.colors.teal600, theme.colors.teal400Dark), + borderColor: null, + }), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // @ts-ignore + [COLOR_STATE.secondary]: (theme, color) => ({ + color: pick(theme, theme.colors.teal700, theme.colors.teal700Dark), + backgroundColor: pick(theme, theme.colors.teal50, theme.colors.teal100Dark), + borderColor: null, }), }; @@ -224,26 +251,23 @@ const orangeColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ - color: pick(theme, theme.colors.orange200, theme.colors.orange600), - // @ts-ignore - backgroundColor: null, - borderColor: pick(theme, theme.colors.orange200, theme.colors.orange700), + color: pick(theme, theme.colors.orange300, theme.colors.orange400Dark), + backgroundColor: pick(theme, theme.colors.orange50, theme.colors.orange100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ - color: theme.colors.white, - backgroundColor: pick(theme, theme.colors.orange400, theme.colors.orange500), - // @ts-ignore + [COLOR_STATE.primary]: (theme, color) => ({ + color: pick(theme, theme.colors.white, theme.colors.orange900Dark), + backgroundColor: pick(theme, theme.colors.orange600, theme.colors.orange400Dark), borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ - color: pick(theme, theme.colors.orange400, theme.colors.orange300), - // @ts-ignore - backgroundColor: null, - borderColor: pick(theme, theme.colors.orange200, theme.colors.orange500), + [COLOR_STATE.secondary]: (theme, color) => ({ + color: pick(theme, theme.colors.orange700, theme.colors.orange700Dark), + backgroundColor: pick(theme, theme.colors.orange50, theme.colors.orange100Dark), + borderColor: null, }), }; @@ -251,26 +275,23 @@ const purpleColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ - color: pick(theme, theme.colors.purple200, theme.colors.purple600), - // @ts-ignore - backgroundColor: null, - borderColor: pick(theme, theme.colors.purple200, theme.colors.purple700), + color: pick(theme, theme.colors.purple300, theme.colors.purple400Dark), + backgroundColor: pick(theme, theme.colors.purple50, theme.colors.purple100Dark), + borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ - color: theme.colors.white, - backgroundColor: pick(theme, theme.colors.purple400, theme.colors.purple500), - // @ts-ignore + [COLOR_STATE.primary]: (theme, color) => ({ + color: pick(theme, theme.colors.white, theme.colors.purple900Dark), + backgroundColor: pick(theme, theme.colors.purple600, theme.colors.purple400Dark), borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ - color: pick(theme, theme.colors.purple400, theme.colors.purple300), - // @ts-ignore - backgroundColor: null, - borderColor: pick(theme, theme.colors.purple200, theme.colors.purple500), + [COLOR_STATE.secondary]: (theme, color) => ({ + color: pick(theme, theme.colors.purple700, theme.colors.purple700Dark), + backgroundColor: pick(theme, theme.colors.purple50, theme.colors.purple100Dark), + borderColor: null, }), }; @@ -278,26 +299,23 @@ const brownColorStates = { // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ - color: pick(theme, theme.colors.brown200, theme.colors.brown600), - // @ts-ignore + color: pick(theme, theme.colors.amber200, theme.colors.amber400Dark), backgroundColor: null, - borderColor: pick(theme, theme.colors.brown200, theme.colors.brown700), + borderColor: pick(theme, theme.colors.amber200, theme.colors.amber400Dark), }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ - color: theme.colors.white, - backgroundColor: pick(theme, theme.colors.brown400, theme.colors.brown500), - // @ts-ignore + [COLOR_STATE.primary]: (theme, color) => ({ + color: pick(theme, theme.colors.white, theme.colors.gray900Dark), + backgroundColor: pick(theme, theme.colors.amber600, theme.colors.amber400Dark), borderColor: null, }), // eslint-disable-next-line @typescript-eslint/no-unused-vars // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ - color: pick(theme, theme.colors.brown400, theme.colors.brown300), - // @ts-ignore + [COLOR_STATE.secondary]: (theme, color) => ({ + color: pick(theme, theme.colors.amber600, theme.colors.amber600Dark), backgroundColor: null, - borderColor: pick(theme, theme.colors.brown200, theme.colors.brown500), + borderColor: pick(theme, theme.colors.amber600, theme.colors.amber600Dark), }), }; @@ -305,21 +323,18 @@ const customColorStates = { // @ts-ignore [COLOR_STATE.disabled]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagFontDisabledRampUnit), - // @ts-ignore backgroundColor: null, borderColor: customOnRamp(color, theme.colors.tagSolidDisabledRampUnit), }), // @ts-ignore - [COLOR_STATE.solid]: (theme, color) => ({ + [COLOR_STATE.primary]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagSolidFontRampUnit), backgroundColor: customOnRamp(color, theme.colors.tagSolidRampUnit), - // @ts-ignore borderColor: null, }), // @ts-ignore - [COLOR_STATE.outline]: (theme, color) => ({ + [COLOR_STATE.secondary]: (theme, color) => ({ color: customOnRamp(color, theme.colors.tagOutlinedFontRampUnit), - // @ts-ignore backgroundColor: null, borderColor: customOnRamp(color, theme.colors.tagOutlinedRampUnit), }), @@ -328,26 +343,28 @@ const customColorStates = { const colorMap = { [KIND.neutral]: neutralColorStates, [KIND.primary]: primaryColorStates, - [KIND.accent]: accentColorStates, - [KIND.positive]: positiveColorStates, - [KIND.warning]: warningColorStates, - [KIND.negative]: negativeColorStates, + [KIND.accent]: blueColorStates, + [KIND.positive]: greenColorStates, + [KIND.warning]: yellowColorStates, + [KIND.negative]: redColorStates, [KIND.black]: primaryColorStates, - [KIND.blue]: accentColorStates, - [KIND.green]: positiveColorStates, - [KIND.red]: negativeColorStates, - [KIND.yellow]: warningColorStates, + [KIND.blue]: blueColorStates, + [KIND.green]: greenColorStates, + [KIND.red]: redColorStates, + [KIND.yellow]: yellowColorStates, [KIND.orange]: orangeColorStates, [KIND.purple]: purpleColorStates, [KIND.brown]: brownColorStates, + [KIND.lime]: limeColorStates, + [KIND.teal]: tealColorStates, [KIND.custom]: customColorStates, }; // @ts-ignore const getColorStateFromProps = (props) => { if (props.$disabled) return COLOR_STATE.disabled; - if (props.$variant === VARIANT.solid) return COLOR_STATE.solid; - return COLOR_STATE.outline; + if (props.$hierarchy === HIERARCHY.primary) return COLOR_STATE.primary; + return COLOR_STATE.secondary; }; export const Action = styled<'span', SharedPropsArg>( @@ -444,7 +461,7 @@ export const Root = styled<'span', SharedPropsArg>( $theme, $kind = KIND.primary, $clickable, - $variant, + $hierarchy, $disabled, $closeable, $isFocusVisible, @@ -452,14 +469,14 @@ export const Root = styled<'span', SharedPropsArg>( $size = SIZE.small, $contentMaxWidth, } = props; - const borderRadius = $theme.borders.tagBorderRadius; + const borderRadius = $size === SIZE.small ? $theme.borders.radius200 : $theme.borders.radius300; const paddingMagnitude = { [SIZE.small]: $theme.sizing.scale300, [SIZE.medium]: $theme.sizing.scale500, [SIZE.large]: $theme.sizing.scale600, }[$size]; - const borderWidth = !$disabled && $variant === VARIANT.solid ? 0 : '2px'; - // @ts-ignore + const borderWidth = + (!$disabled && $hierarchy === HIERARCHY.primary) || $kind !== KIND.custom ? 0 : '2px'; const { color, backgroundColor, borderColor } = colorMap[$kind][getColorStateFromProps(props)]( $theme, $color diff --git a/src/tag/tag.tsx b/src/tag/tag.tsx index 45832a2684..f2996a9eb4 100644 --- a/src/tag/tag.tsx +++ b/src/tag/tag.tsx @@ -12,7 +12,7 @@ import { StartEnhancerContainer as StyledStartEnhancerContainer, Text as StyledText, } from './styled-components'; -import { KIND, VARIANT, SIZE } from './constants'; +import { KIND, HIERARCHY, SIZE } from './constants'; import { getTextFromChildren } from './utils'; import type { TagProps, SharedPropsArg } from './types'; import DeleteIcon from '../icon/delete'; @@ -45,7 +45,7 @@ const Tag = React.forwardRef<HTMLSpanElement, TagProps>((props, ref) => { overrides = {}, startEnhancer, title, - variant = VARIANT.light, + hierarchy = HIERARCHY.secondary, } = props; const [focusVisible, setFocusVisible] = React.useState(false); @@ -115,7 +115,7 @@ const Tag = React.forwardRef<HTMLSpanElement, TagProps>((props, ref) => { $isFocused: isFocused, $isHovered: isHovered, $kind: kind, - $variant: variant, + $hierarchy: hierarchy, $isFocusVisible: focusVisible, $size: size, }; diff --git a/src/tag/types.ts b/src/tag/types.ts index 553b5fb358..da01154932 100644 --- a/src/tag/types.ts +++ b/src/tag/types.ts @@ -7,14 +7,14 @@ LICENSE file in the root directory of this source tree. import type * as React from 'react'; import type { Override } from '../helpers/overrides'; -import { KIND, VARIANT, SIZE } from './constants'; +import { KIND, HIERARCHY, SIZE } from './constants'; export const TagKind = Object.freeze(KIND); -export const TagVariant = Object.freeze(VARIANT); +export const TagHierarchy = Object.freeze(HIERARCHY); export const TagSize = Object.freeze(SIZE); export type TagKind = keyof typeof TagKind; -export type TagVariant = (typeof TagVariant)[keyof typeof TagVariant]; +export type TagHierarchy = (typeof TagHierarchy)[keyof typeof TagHierarchy]; export type TagSize = keyof typeof TagSize; export type TagOverrides = { @@ -37,8 +37,8 @@ export type TagProps = { isHovered?: boolean; /** Defines tags look by purpose. Set it to one of KIND[key] values. Defaults to KIND.primary */ kind?: TagKind; - /** Defines tags look. Set it to one of VARIANT[key] values. Defaults to VARIANT.light */ - variant?: TagVariant; + /** Defines tags look. Set it to one of HIERARCHY[key] values. Defaults to HIERARCHY.secondary */ + hierarchy?: TagHierarchy; /** Component or String value for label of tag. Default is empty string. */ children?: React.ReactNode; /** The color theme to be applied to a Tag. Default is `KIND.primary`. */ @@ -68,7 +68,7 @@ export type SharedPropsArg = { $isFocused?: boolean; $isHovered?: boolean; $kind?: string; - $variant?: string; + $hierarchy?: string; $isFocusVisible?: boolean; $size?: string; $contentMaxWidth?: string | null; diff --git a/src/themes/dark-theme/__tests__/create-dark-theme.test.ts b/src/themes/dark-theme/__tests__/create-dark-theme.test.ts new file mode 100644 index 0000000000..ab6ff88a9d --- /dev/null +++ b/src/themes/dark-theme/__tests__/create-dark-theme.test.ts @@ -0,0 +1,12 @@ +import { DarkTheme } from '../dark-theme'; +import createDarkTheme from '../create-dark-theme'; + +describe('createDarkTheme', () => { + test('without any overrides, returns the same value as DarkTheme variable', () => { + expect(createDarkTheme()).toEqual(DarkTheme); + }); + + test('ensure property `name` is populated', () => { + expect(createDarkTheme().name).toEqual('dark-theme'); + }); +}); diff --git a/src/themes/dark-theme/color-component-tokens.ts b/src/themes/dark-theme/color-component-tokens.ts index da4416f171..0ccf89c9b8 100644 --- a/src/themes/dark-theme/color-component-tokens.ts +++ b/src/themes/dark-theme/color-component-tokens.ts @@ -4,132 +4,135 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import defaultFoundationColorTokens from './color-tokens'; -import type { FoundationColorTokens, ComponentColorTokens } from '../types'; +import type { SemanticColors, ComponentColors } from '../types'; +import getSemanticColors from './color-semantic-tokens'; +import { primitiveDarkColors } from '../../tokens'; + +const defaultSemanticColors = getSemanticColors(); const tagHoverBackground = `rgba(255, 255, 255, 0.2)`; /* ---- Component colors ---- */ -// TODO(#2318) Make it a plain object in the next v11 major version -// with values taken from `defaultFoundationColorTokens`. -// Due to the legacy `createTheme` type the values -// need to be overrideable through primitives -export default ( - themePrimitives: FoundationColorTokens = defaultFoundationColorTokens -): ComponentColorTokens => ({ - bannerActionLowInfo: themePrimitives.accent600, - bannerActionLowNegative: themePrimitives.negative600, - bannerActionLowPositive: themePrimitives.positive600, - bannerActionLowWarning: themePrimitives.warning600, - bannerActionHighInfo: themePrimitives.accent500, - bannerActionHighNegative: themePrimitives.negative600, - bannerActionHighPositive: themePrimitives.positive600, - bannerActionHighWarning: themePrimitives.warning600, +export default (semanticColors: SemanticColors = defaultSemanticColors): ComponentColors => ({ + //Banner + bannerActionLowInfo: primitiveDarkColors.blue100Dark, + bannerActionLowNegative: primitiveDarkColors.red200Dark, + bannerActionLowPositive: primitiveDarkColors.green200Dark, + bannerActionLowWarning: primitiveDarkColors.yellow200Dark, + bannerActionHighInfo: primitiveDarkColors.blue300Dark, + bannerActionHighNegative: primitiveDarkColors.red300Dark, + bannerActionHighPositive: primitiveDarkColors.green300Dark, + bannerActionHighWarning: primitiveDarkColors.yellow300Dark, + + // BottomNavigation + bottomNavigationText: primitiveDarkColors.gray600Dark, + bottomNavigationSelectedText: semanticColors.contentPrimary, // Buttons - buttonPrimaryFill: themePrimitives.primaryA, - buttonPrimaryText: themePrimitives.black, - buttonPrimaryHover: themePrimitives.primary100, - buttonPrimaryActive: themePrimitives.primary200, - buttonPrimarySelectedText: themePrimitives.black, - buttonPrimarySelectedFill: themePrimitives.primary200, - buttonPrimarySpinnerForeground: themePrimitives.primary700, - buttonPrimarySpinnerBackground: themePrimitives.primary300, - buttonSecondaryFill: themePrimitives.primary700, - buttonSecondaryText: themePrimitives.primary, - buttonSecondaryHover: themePrimitives.primary600, - buttonSecondaryActive: themePrimitives.primary500, - buttonSecondarySelectedText: themePrimitives.primary, - buttonSecondarySelectedFill: themePrimitives.primary500, - buttonSecondarySpinnerForeground: themePrimitives.white, - buttonSecondarySpinnerBackground: themePrimitives.primary400, + buttonPrimaryFill: semanticColors.backgroundInversePrimary, + + buttonPrimaryText: semanticColors.contentInversePrimary, + buttonPrimaryHover: primitiveDarkColors.gray700Dark, + buttonPrimaryActive: primitiveDarkColors.gray600Dark, + buttonPrimarySelectedFill: semanticColors.backgroundInversePrimary, + buttonPrimarySelectedText: semanticColors.contentInversePrimary, + buttonPrimarySpinnerForeground: semanticColors.backgroundAccent, + buttonPrimarySpinnerBackground: semanticColors.backgroundPrimary, + buttonSecondaryFill: semanticColors.backgroundTertiary, + buttonSecondaryText: semanticColors.contentPrimary, + buttonSecondaryHover: primitiveDarkColors.gray300Dark, + buttonSecondaryActive: primitiveDarkColors.gray400Dark, + buttonSecondarySelectedFill: semanticColors.backgroundInversePrimary, + buttonSecondarySelectedText: semanticColors.contentInversePrimary, + buttonSecondarySpinnerForeground: semanticColors.backgroundAccent, + buttonSecondarySpinnerBackground: semanticColors.backgroundPrimary, buttonTertiaryFill: 'transparent', - buttonTertiaryText: themePrimitives.primary, - buttonTertiaryHover: themePrimitives.primary700, - buttonTertiaryActive: themePrimitives.primary600, - buttonTertiarySelectedText: themePrimitives.primary, - buttonTertiarySelectedFill: themePrimitives.primary600, - buttonTertiaryDisabledActiveFill: themePrimitives.primary700, - buttonTertiaryDisabledActiveText: themePrimitives.mono300, - buttonTertiarySpinnerForeground: themePrimitives.primary50, - buttonTertiarySpinnerBackground: themePrimitives.primary500, - buttonDisabledFill: themePrimitives.mono600, - buttonDisabledText: themePrimitives.mono300, - buttonDisabledActiveFill: themePrimitives.mono200, - buttonDisabledActiveText: themePrimitives.primary500, - buttonDisabledSpinnerForeground: themePrimitives.mono200, - buttonDisabledSpinnerBackground: themePrimitives.mono400, + buttonTertiaryText: semanticColors.contentPrimary, + buttonTertiaryHover: primitiveDarkColors.gray100Dark, + buttonTertiaryActive: primitiveDarkColors.gray200Dark, + buttonTertiarySelectedFill: semanticColors.backgroundInversePrimary, + buttonTertiarySelectedText: semanticColors.contentInversePrimary, + buttonTertiaryDisabledActiveFill: semanticColors.backgroundStateDisabled, + buttonTertiaryDisabledActiveText: semanticColors.contentStateDisabled, + buttonTertiarySpinnerForeground: semanticColors.backgroundAccent, + buttonTertiarySpinnerBackground: semanticColors.backgroundTertiary, + buttonDisabledFill: semanticColors.backgroundStateDisabled, + buttonDisabledText: semanticColors.contentStateDisabled, + buttonDisabledActiveFill: semanticColors.backgroundStateDisabled, + buttonDisabledActiveText: semanticColors.contentStateDisabled, + buttonDisabledSpinnerForeground: semanticColors.contentStateDisabled, + buttonDisabledSpinnerBackground: semanticColors.backgroundPrimary, // Breadcrumbs - breadcrumbsText: themePrimitives.white, + breadcrumbsText: semanticColors.contentPrimary, - breadcrumbsSeparatorFill: themePrimitives.mono200, + breadcrumbsSeparatorFill: semanticColors.contentTertiary, // Datepicker - calendarBackground: themePrimitives.mono800, - - calendarForeground: themePrimitives.white, - calendarForegroundDisabled: themePrimitives.mono300, - calendarHeaderBackground: themePrimitives.mono800, - calendarHeaderForeground: themePrimitives.primary, - calendarHeaderBackgroundActive: themePrimitives.primary600, - calendarHeaderForegroundDisabled: themePrimitives.primary500, - calendarDayForegroundPseudoSelected: themePrimitives.primary, - calendarDayBackgroundPseudoSelectedHighlighted: themePrimitives.primary600, - calendarDayForegroundPseudoSelectedHighlighted: themePrimitives.primary, - calendarDayBackgroundSelected: themePrimitives.primary, - calendarDayForegroundSelected: themePrimitives.black, - calendarDayBackgroundSelectedHighlighted: themePrimitives.primary100, - calendarDayForegroundSelectedHighlighted: themePrimitives.black, + calendarBackground: semanticColors.backgroundPrimary, + + calendarForeground: semanticColors.contentPrimary, + calendarForegroundDisabled: semanticColors.contentStateDisabled, + calendarHeaderBackground: semanticColors.backgroundPrimary, + calendarHeaderForeground: semanticColors.contentPrimary, + calendarHeaderBackgroundActive: semanticColors.backgroundInversePrimary, + calendarHeaderForegroundDisabled: semanticColors.contentStateDisabled, + calendarDayForegroundPseudoSelected: semanticColors.backgroundInversePrimary, + calendarDayBackgroundPseudoSelectedHighlighted: semanticColors.backgroundTertiary, + calendarDayForegroundPseudoSelectedHighlighted: semanticColors.contentPrimary, + calendarDayBackgroundSelected: semanticColors.backgroundInversePrimary, + calendarDayForegroundSelected: semanticColors.contentInversePrimary, + calendarDayBackgroundSelectedHighlighted: semanticColors.backgroundInversePrimary, + calendarDayForegroundSelectedHighlighted: semanticColors.contentInversePrimary, // Combobox - comboboxListItemFocus: themePrimitives.mono600, + comboboxListItemFocus: semanticColors.backgroundSecondary, - comboboxListItemHover: themePrimitives.mono500, + comboboxListItemHover: semanticColors.backgroundTertiary, // FileUploader - fileUploaderBackgroundColor: themePrimitives.mono700, + fileUploaderBackgroundColor: semanticColors.backgroundSecondary, - fileUploaderBackgroundColorActive: themePrimitives.mono600, - fileUploaderBorderColorActive: themePrimitives.primary, - fileUploaderBorderColorDefault: themePrimitives.mono500, - fileUploaderMessageColor: themePrimitives.mono100, + fileUploaderBackgroundColorActive: semanticColors.backgroundPrimary, + fileUploaderBorderColorActive: semanticColors.borderSelected, + fileUploaderBorderColorDefault: semanticColors.borderOpaque, + fileUploaderMessageColor: semanticColors.contentPrimary, // Links - linkText: themePrimitives.primary, + linkText: semanticColors.contentPrimary, - linkVisited: themePrimitives.primary100, - linkHover: themePrimitives.primary200, - linkActive: themePrimitives.primary300, + linkVisited: primitiveDarkColors.gray500Dark, + linkHover: primitiveDarkColors.gray700Dark, + linkActive: primitiveDarkColors.gray600Dark, // List - listHeaderFill: themePrimitives.mono600, + listHeaderFill: semanticColors.backgroundPrimary, - listBodyFill: themePrimitives.mono800, + listBodyFill: semanticColors.backgroundPrimary, // ProgressSteps - progressStepsCompletedText: themePrimitives.black, + progressStepsCompletedText: semanticColors.contentInversePrimary, - progressStepsCompletedFill: themePrimitives.primary, - progressStepsActiveText: themePrimitives.black, - progressStepsActiveFill: themePrimitives.primary, + progressStepsCompletedFill: semanticColors.backgroundInversePrimary, + progressStepsActiveText: semanticColors.contentInversePrimary, + progressStepsActiveFill: semanticColors.backgroundInversePrimary, // Modal - modalCloseColor: themePrimitives.mono300, + modalCloseColor: semanticColors.contentPrimary, - modalCloseColorHover: themePrimitives.mono400, - modalCloseColorFocus: themePrimitives.mono400, + modalCloseColorHover: primitiveDarkColors.gray700Dark, + modalCloseColorFocus: primitiveDarkColors.gray600Dark, // Notification - notificationInfoBackground: themePrimitives.accent700, + notificationInfoBackground: semanticColors.backgroundAccentLight, - notificationInfoText: themePrimitives.primaryA, - notificationPositiveBackground: themePrimitives.positive700, - notificationPositiveText: themePrimitives.primaryA, - notificationWarningBackground: themePrimitives.warning700, - notificationWarningText: themePrimitives.primaryA, - notificationNegativeBackground: themePrimitives.negative700, - notificationNegativeText: themePrimitives.primaryA, + notificationInfoText: semanticColors.contentPrimary, + notificationPositiveBackground: semanticColors.backgroundPositiveLight, + notificationPositiveText: semanticColors.contentPrimary, + notificationWarningBackground: semanticColors.backgroundWarningLight, + notificationWarningText: semanticColors.contentPrimary, + notificationNegativeBackground: semanticColors.backgroundNegativeLight, + notificationNegativeText: semanticColors.contentPrimary, // Tag @@ -137,7 +140,7 @@ export default ( tagFontDisabledRampUnit: '600', tagSolidFontRampUnit: '0', - tagSolidRampUnit: '500', + tagSolidRampUnit: '600', tagOutlinedFontRampUnit: '500', tagOutlinedRampUnit: '500', @@ -157,262 +160,263 @@ export default ( tagOutlinedFontHoverRampUnit: '100', // Neutral - tagNeutralOutlinedFont: themePrimitives.mono100, - - tagNeutralOutlinedBackground: themePrimitives.mono200, - tagNeutralSolidFont: themePrimitives.black, + tagNeutralFontDisabled: primitiveDarkColors.gray400Dark, - // not much we can do to get the correct gray here - tagNeutralSolidBackground: themePrimitives.primary200, - - tagNeutralFontDisabled: themePrimitives.mono400, - tagNeutralOutlinedDisabled: themePrimitives.mono500, + tagNeutralOutlinedDisabled: primitiveDarkColors.gray400Dark, + tagNeutralSolidFont: primitiveDarkColors.gray900Dark, + tagNeutralSolidBackground: primitiveDarkColors.gray400Dark, + tagNeutralOutlinedBackground: primitiveDarkColors.gray100Dark, + tagNeutralOutlinedFont: primitiveDarkColors.gray700Dark, // Deprecated - tagNeutralSolidHover: themePrimitives.mono600, - - tagNeutralSolidActive: themePrimitives.mono500, - tagNeutralSolidDisabled: themePrimitives.mono700, - tagNeutralSolidFontHover: themePrimitives.mono200, - tagNeutralLightBackground: themePrimitives.mono800, - tagNeutralLightHover: themePrimitives.mono800, - tagNeutralLightActive: themePrimitives.mono700, - tagNeutralLightDisabled: themePrimitives.mono700, - tagNeutralLightFont: themePrimitives.mono200, - tagNeutralLightFontHover: themePrimitives.mono200, - tagNeutralOutlinedActive: themePrimitives.mono400, - tagNeutralOutlinedFontHover: themePrimitives.mono100, + tagNeutralSolidHover: primitiveDarkColors.gray700Dark, + + tagNeutralSolidActive: primitiveDarkColors.gray600Dark, + tagNeutralSolidDisabled: primitiveDarkColors.gray100Dark, + tagNeutralSolidFontHover: primitiveDarkColors.gray800Dark, + tagNeutralLightBackground: primitiveDarkColors.gray100Dark, + tagNeutralLightHover: primitiveDarkColors.gray800Dark, + tagNeutralLightActive: primitiveDarkColors.gray700Dark, + tagNeutralLightDisabled: primitiveDarkColors.gray400Dark, + tagNeutralLightFont: primitiveDarkColors.gray900Dark, + tagNeutralLightFontHover: primitiveDarkColors.gray800Dark, + tagNeutralOutlinedActive: primitiveDarkColors.gray700Dark, + tagNeutralOutlinedFontHover: primitiveDarkColors.gray700Dark, tagNeutralOutlinedHover: tagHoverBackground, // Primary - tagPrimaryOutlinedFont: themePrimitives.primary200, + tagPrimaryOutlinedFont: primitiveDarkColors.gray700Dark, - tagPrimaryOutlinedBackground: themePrimitives.primary400, - tagPrimarySolidFont: themePrimitives.black, - tagPrimarySolidBackground: themePrimitives.primary200, - tagPrimaryFontDisabled: themePrimitives.primary600, - tagPrimaryOutlinedDisabled: themePrimitives.primary700, + tagPrimaryOutlinedBackground: primitiveDarkColors.gray100Dark, + tagPrimarySolidFont: primitiveDarkColors.gray900Dark, + tagPrimarySolidBackground: primitiveDarkColors.gray400Dark, + tagPrimaryFontDisabled: primitiveDarkColors.gray400Dark, + tagPrimaryOutlinedDisabled: primitiveDarkColors.gray400Dark, // Deprecated - tagPrimarySolidHover: themePrimitives.primary700, - - tagPrimarySolidActive: themePrimitives.primary400, - tagPrimarySolidDisabled: themePrimitives.primary700, - tagPrimarySolidFontHover: themePrimitives.primary100, - tagPrimaryLightBackground: themePrimitives.primary700, - tagPrimaryLightHover: themePrimitives.primary700, - tagPrimaryLightActive: themePrimitives.primary600, - tagPrimaryLightDisabled: themePrimitives.primary700, - tagPrimaryLightFont: themePrimitives.primary100, - tagPrimaryLightFontHover: themePrimitives.primary100, - tagPrimaryOutlinedActive: themePrimitives.primary600, - tagPrimaryOutlinedFontHover: themePrimitives.primary200, + tagPrimarySolidHover: primitiveDarkColors.gray300Dark, + + tagPrimarySolidActive: primitiveDarkColors.gray200Dark, + tagPrimarySolidDisabled: primitiveDarkColors.gray100Dark, + tagPrimarySolidFontHover: primitiveDarkColors.gray800Dark, + tagPrimaryLightBackground: primitiveDarkColors.gray100Dark, + tagPrimaryLightHover: primitiveDarkColors.gray200Dark, + tagPrimaryLightActive: primitiveDarkColors.gray300Dark, + tagPrimaryLightDisabled: primitiveDarkColors.gray400Dark, + tagPrimaryLightFont: primitiveDarkColors.gray900Dark, + tagPrimaryLightFontHover: primitiveDarkColors.gray800Dark, + tagPrimaryOutlinedActive: primitiveDarkColors.gray700Dark, + tagPrimaryOutlinedFontHover: primitiveDarkColors.gray700Dark, tagPrimaryOutlinedHover: tagHoverBackground, // Accent - tagAccentOutlinedFont: themePrimitives.accent200, + tagAccentOutlinedFont: primitiveDarkColors.blue700Dark, - tagAccentOutlinedBackground: themePrimitives.accent500, - tagAccentSolidFont: themePrimitives.white, - tagAccentSolidBackground: themePrimitives.accent500, - tagAccentFontDisabled: themePrimitives.accent600, - tagAccentOutlinedDisabled: themePrimitives.accent700, + tagAccentOutlinedBackground: primitiveDarkColors.blue100Dark, + tagAccentSolidFont: primitiveDarkColors.blue900Dark, + tagAccentSolidBackground: primitiveDarkColors.blue500Dark, + tagAccentFontDisabled: primitiveDarkColors.blue400Dark, + tagAccentOutlinedDisabled: primitiveDarkColors.blue400Dark, // Deprecated - tagAccentSolidHover: themePrimitives.accent500, - - tagAccentSolidActive: themePrimitives.accent400, - tagAccentSolidDisabled: themePrimitives.accent700, - tagAccentSolidFontHover: themePrimitives.accent100, - tagAccentLightBackground: themePrimitives.accent700, - tagAccentLightHover: themePrimitives.accent700, - tagAccentLightActive: themePrimitives.accent600, - tagAccentLightDisabled: themePrimitives.accent700, - tagAccentLightFont: themePrimitives.accent100, - tagAccentLightFontHover: themePrimitives.accent100, - tagAccentOutlinedActive: themePrimitives.accent300, - tagAccentOutlinedFontHover: themePrimitives.accent200, + tagAccentSolidHover: primitiveDarkColors.blue300Dark, + + tagAccentSolidActive: primitiveDarkColors.blue200Dark, + tagAccentSolidDisabled: primitiveDarkColors.blue100Dark, + tagAccentSolidFontHover: primitiveDarkColors.gray800Dark, + tagAccentLightBackground: primitiveDarkColors.blue100Dark, + tagAccentLightHover: primitiveDarkColors.blue200Dark, + tagAccentLightActive: primitiveDarkColors.blue300Dark, + tagAccentLightDisabled: primitiveDarkColors.blue400Dark, + tagAccentLightFont: primitiveDarkColors.blue900Dark, + tagAccentLightFontHover: primitiveDarkColors.blue800Dark, + tagAccentOutlinedActive: primitiveDarkColors.blue700Dark, + tagAccentOutlinedFontHover: primitiveDarkColors.blue700Dark, tagAccentOutlinedHover: tagHoverBackground, // Positive - tagPositiveOutlinedFont: themePrimitives.positive300, + tagPositiveFontDisabled: primitiveDarkColors.green400Dark, - tagPositiveOutlinedBackground: themePrimitives.positive500, - tagPositiveSolidFont: themePrimitives.white, - tagPositiveSolidBackground: themePrimitives.positive500, - tagPositiveFontDisabled: themePrimitives.positive600, - tagPositiveOutlinedDisabled: themePrimitives.positive700, + tagPositiveOutlinedDisabled: primitiveDarkColors.green400Dark, + tagPositiveSolidFont: primitiveDarkColors.green900Dark, + tagPositiveSolidBackground: primitiveDarkColors.green500Dark, + tagPositiveOutlinedBackground: primitiveDarkColors.green100Dark, + tagPositiveOutlinedFont: primitiveDarkColors.green700Dark, // Deprecated - tagPositiveSolidHover: themePrimitives.positive500, - - tagPositiveSolidActive: themePrimitives.positive400, - tagPositiveSolidDisabled: themePrimitives.positive700, - tagPositiveSolidFontHover: themePrimitives.positive100, - tagPositiveLightBackground: themePrimitives.positive700, - tagPositiveLightHover: themePrimitives.positive700, - tagPositiveLightActive: themePrimitives.positive600, - tagPositiveLightDisabled: themePrimitives.positive700, - tagPositiveLightFont: themePrimitives.positive100, - tagPositiveLightFontHover: themePrimitives.positive100, - tagPositiveOutlinedActive: themePrimitives.positive300, - tagPositiveOutlinedFontHover: themePrimitives.positive300, + tagPositiveSolidHover: primitiveDarkColors.green300Dark, + + tagPositiveSolidActive: primitiveDarkColors.green200Dark, + tagPositiveSolidDisabled: primitiveDarkColors.green100Dark, + tagPositiveSolidFontHover: primitiveDarkColors.gray800Dark, + tagPositiveLightBackground: primitiveDarkColors.green100Dark, + tagPositiveLightHover: primitiveDarkColors.green200Dark, + tagPositiveLightActive: primitiveDarkColors.green300Dark, + tagPositiveLightDisabled: primitiveDarkColors.green400Dark, + tagPositiveLightFont: primitiveDarkColors.green900Dark, + tagPositiveLightFontHover: primitiveDarkColors.green800Dark, + tagPositiveOutlinedActive: primitiveDarkColors.green700Dark, + tagPositiveOutlinedFontHover: primitiveDarkColors.green700Dark, tagPositiveOutlinedHover: tagHoverBackground, // Warning - tagWarningOutlinedFont: themePrimitives.warning300, + tagWarningOutlinedFont: primitiveDarkColors.yellow700Dark, - tagWarningOutlinedBackground: themePrimitives.warning500, - tagWarningSolidFont: themePrimitives.black, - tagWarningSolidBackground: themePrimitives.warning500, - tagWarningFontDisabled: themePrimitives.warning600, - tagWarningOutlinedDisabled: themePrimitives.warning700, + tagWarningOutlinedBackground: primitiveDarkColors.yellow100Dark, + tagWarningSolidFont: primitiveDarkColors.yellow50Dark, + tagWarningSolidBackground: primitiveDarkColors.yellow700Dark, + tagWarningFontDisabled: primitiveDarkColors.yellow400Dark, + tagWarningOutlinedDisabled: primitiveDarkColors.yellow400Dark, // Deprecated - tagWarningSolidHover: themePrimitives.warning500, - - tagWarningSolidActive: themePrimitives.warning400, - tagWarningSolidDisabled: themePrimitives.warning700, - tagWarningSolidFontHover: themePrimitives.warning100, - tagWarningLightBackground: themePrimitives.warning700, - tagWarningLightHover: themePrimitives.warning700, - tagWarningLightActive: themePrimitives.warning600, - tagWarningLightDisabled: themePrimitives.warning700, - tagWarningLightFont: themePrimitives.warning100, - tagWarningLightFontHover: themePrimitives.warning100, - tagWarningOutlinedActive: themePrimitives.warning300, - tagWarningOutlinedFontHover: themePrimitives.warning300, + tagWarningSolidHover: primitiveDarkColors.yellow300Dark, + + tagWarningSolidActive: primitiveDarkColors.yellow200Dark, + tagWarningSolidDisabled: primitiveDarkColors.yellow100Dark, + tagWarningSolidFontHover: primitiveDarkColors.gray800Dark, + tagWarningLightBackground: primitiveDarkColors.yellow100Dark, + tagWarningLightHover: primitiveDarkColors.yellow200Dark, + tagWarningLightActive: primitiveDarkColors.yellow300Dark, + tagWarningLightDisabled: primitiveDarkColors.yellow400Dark, + tagWarningLightFont: primitiveDarkColors.yellow900Dark, + tagWarningLightFontHover: primitiveDarkColors.yellow800Dark, + tagWarningOutlinedActive: primitiveDarkColors.yellow700Dark, + tagWarningOutlinedFontHover: primitiveDarkColors.yellow700Dark, tagWarningOutlinedHover: tagHoverBackground, // Negative - tagNegativeOutlinedFont: themePrimitives.negative300, + tagNegativeOutlinedFont: primitiveDarkColors.red700Dark, - tagNegativeOutlinedBackground: themePrimitives.negative500, - tagNegativeSolidFont: themePrimitives.white, - tagNegativeSolidBackground: themePrimitives.negative500, - tagNegativeFontDisabled: themePrimitives.negative600, - tagNegativeOutlinedDisabled: themePrimitives.negative700, + tagNegativeOutlinedBackground: primitiveDarkColors.red100Dark, + tagNegativeSolidFont: primitiveDarkColors.gray900Dark, + tagNegativeSolidBackground: primitiveDarkColors.red500Dark, + tagNegativeFontDisabled: primitiveDarkColors.red400Dark, + tagNegativeOutlinedDisabled: primitiveDarkColors.red400Dark, // Deprecated - tagNegativeSolidHover: themePrimitives.negative500, - - tagNegativeSolidActive: themePrimitives.negative400, - tagNegativeSolidDisabled: themePrimitives.negative700, - tagNegativeSolidFontHover: themePrimitives.negative100, - tagNegativeLightBackground: themePrimitives.negative700, - tagNegativeLightHover: themePrimitives.negative700, - tagNegativeLightActive: themePrimitives.negative600, - tagNegativeLightDisabled: themePrimitives.negative700, - tagNegativeLightFont: themePrimitives.negative100, - tagNegativeLightFontHover: themePrimitives.negative100, - tagNegativeOutlinedActive: themePrimitives.negative300, - tagNegativeOutlinedFontHover: themePrimitives.negative300, + tagNegativeSolidHover: primitiveDarkColors.red300Dark, + + tagNegativeSolidActive: primitiveDarkColors.red200Dark, + tagNegativeSolidDisabled: primitiveDarkColors.red100Dark, + tagNegativeSolidFontHover: primitiveDarkColors.gray800Dark, + tagNegativeLightBackground: primitiveDarkColors.red100Dark, + tagNegativeLightHover: primitiveDarkColors.red200Dark, + tagNegativeLightActive: primitiveDarkColors.red300Dark, + tagNegativeLightDisabled: primitiveDarkColors.red400Dark, + tagNegativeLightFont: primitiveDarkColors.red900Dark, + tagNegativeLightFontHover: primitiveDarkColors.red800Dark, + tagNegativeOutlinedActive: primitiveDarkColors.red700Dark, + tagNegativeOutlinedFontHover: primitiveDarkColors.red700Dark, tagNegativeOutlinedHover: tagHoverBackground, // Table - tableHeadBackgroundColor: themePrimitives.mono700, + tableHeadBackgroundColor: semanticColors.backgroundPrimary, - tableBackground: themePrimitives.mono800, - tableStripedBackground: themePrimitives.mono700, - tableFilter: themePrimitives.mono400, - tableFilterHeading: themePrimitives.mono300, - tableFilterBackground: themePrimitives.mono700, - tableFilterFooterBackground: themePrimitives.mono800, + tableBackground: semanticColors.backgroundPrimary, + tableStripedBackground: semanticColors.backgroundSecondary, + tableFilter: semanticColors.contentTertiary, + tableFilterHeading: semanticColors.contentPrimary, + tableFilterBackground: semanticColors.backgroundPrimary, + tableFilterFooterBackground: semanticColors.backgroundSecondary, // Toast - toastText: themePrimitives.white, - - toastPrimaryText: themePrimitives.white, - toastInfoBackground: themePrimitives.accent400, - toastInfoText: themePrimitives.white, - toastPositiveBackground: themePrimitives.positive400, - toastPositiveText: themePrimitives.white, - toastWarningBackground: themePrimitives.warning400, - toastWarningText: themePrimitives.black, - toastNegativeBackground: themePrimitives.negative400, - toastNegativeText: themePrimitives.white, + toastText: semanticColors.contentOnColor, + + toastPrimaryText: semanticColors.contentOnColor, + toastInfoBackground: semanticColors.backgroundAccent, + toastInfoText: semanticColors.contentOnColor, + toastPositiveBackground: semanticColors.backgroundPositive, + toastPositiveText: semanticColors.contentOnColor, + toastWarningBackground: semanticColors.backgroundWarning, + toastWarningText: semanticColors.contentOnColorInverse, + toastNegativeBackground: semanticColors.backgroundNegative, + toastNegativeText: semanticColors.contentOnColor, // Toggle - toggleFill: themePrimitives.mono300, + toggleFill: semanticColors.backgroundPrimary, - toggleFillChecked: themePrimitives.primary, - toggleFillDisabled: themePrimitives.mono600, - toggleTrackFill: themePrimitives.mono400, - toggleTrackFillDisabled: themePrimitives.mono700, + toggleFillChecked: semanticColors.contentPrimary, + toggleFillDisabled: semanticColors.contentStateDisabled, + toggleTrackFill: semanticColors.backgroundTertiary, + toggleTrackFillDisabled: semanticColors.backgroundStateDisabled, // Tick - tickFill: themePrimitives.mono1000, - - tickFillHover: themePrimitives.mono700, - tickFillActive: themePrimitives.mono600, - tickFillSelected: themePrimitives.primary, - tickFillSelectedHover: themePrimitives.primary50, - tickFillSelectedHoverActive: themePrimitives.primary100, - tickFillError: themePrimitives.negative700, - tickFillErrorHover: themePrimitives.negative600, - tickFillErrorHoverActive: themePrimitives.negative500, - tickFillErrorSelected: themePrimitives.negative500, - tickFillErrorSelectedHover: themePrimitives.negative600, - tickFillErrorSelectedHoverActive: themePrimitives.negative700, - tickFillDisabled: themePrimitives.mono500, - tickBorder: themePrimitives.mono300, - tickBorderError: themePrimitives.negative400, - tickMarkFill: themePrimitives.black, - tickMarkFillError: themePrimitives.white, - tickMarkFillDisabled: themePrimitives.mono800, + tickFill: semanticColors.backgroundPrimary, + + tickFillHover: primitiveDarkColors.gray100Dark, + tickFillActive: primitiveDarkColors.gray200Dark, + tickFillSelected: semanticColors.contentPrimary, + tickFillSelectedHover: primitiveDarkColors.gray800Dark, + tickFillSelectedHoverActive: primitiveDarkColors.gray700Dark, + tickFillError: semanticColors.backgroundPrimary, + tickFillErrorHover: primitiveDarkColors.gray100Dark, + tickFillErrorHoverActive: primitiveDarkColors.gray200Dark, + tickFillErrorSelected: semanticColors.contentNegative, + tickFillErrorSelectedHover: primitiveDarkColors.red500Dark, + tickFillErrorSelectedHoverActive: primitiveDarkColors.red400Dark, + tickFillDisabled: semanticColors.backgroundStateDisabled, + tickBorder: semanticColors.contentTertiary, + tickBorderError: semanticColors.borderNegative, + tickMarkFill: semanticColors.contentInversePrimary, + tickMarkFillError: semanticColors.contentOnColor, + tickMarkFillDisabled: semanticColors.contentInversePrimary, // Slider/Toggle sliderTrackFill: 'transparent', - sliderHandleFill: themePrimitives.primaryA, - sliderHandleFillDisabled: themePrimitives.primary500, - sliderHandleInnerFill: themePrimitives.primaryA, - sliderTrackFillHover: themePrimitives.mono500, - sliderTrackFillActive: themePrimitives.mono400, - sliderTrackFillDisabled: themePrimitives.mono700, - sliderHandleInnerFillDisabled: themePrimitives.mono500, - sliderHandleInnerFillSelectedHover: themePrimitives.primary600, - sliderHandleInnerFillSelectedActive: themePrimitives.primary700, - - // Input - inputBorder: themePrimitives.mono600, - - inputFill: themePrimitives.mono600, - inputFillActive: themePrimitives.mono500, - inputFillError: themePrimitives.negative700, - inputFillDisabled: themePrimitives.mono800, - inputFillPositive: themePrimitives.positive700, - inputTextDisabled: themePrimitives.mono500, - inputBorderError: themePrimitives.negative400, - inputBorderPositive: themePrimitives.positive400, - inputEnhancerFill: themePrimitives.mono500, - inputEnhancerFillDisabled: themePrimitives.mono700, - inputEnhancerTextDisabled: themePrimitives.mono500, - inputPlaceholder: themePrimitives.mono300, - inputPlaceholderDisabled: themePrimitives.mono500, + sliderHandleFill: semanticColors.contentPrimary, + sliderHandleFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFill: semanticColors.contentPrimary, + sliderTrackFillHover: primitiveDarkColors.gray300Dark, + sliderTrackFillActive: primitiveDarkColors.gray400Dark, + sliderTrackFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFillSelectedHover: primitiveDarkColors.gray600Dark, + sliderHandleInnerFillSelectedActive: primitiveDarkColors.gray700Dark, + + // Inputs + inputBorder: semanticColors.borderOpaque, + + inputFill: semanticColors.backgroundSecondary, + inputFillError: semanticColors.backgroundPrimary, + inputFillDisabled: semanticColors.backgroundStateDisabled, + inputFillActive: semanticColors.backgroundPrimary, + inputFillPositive: semanticColors.backgroundPrimary, + inputTextDisabled: semanticColors.contentStateDisabled, + inputBorderError: semanticColors.borderNegative, + inputBorderPositive: semanticColors.borderPositive, + inputEnhancerFill: semanticColors.contentPrimary, + inputEnhancerFillDisabled: semanticColors.contentStateDisabled, + inputEnhancerTextDisabled: semanticColors.contentStateDisabled, + inputPlaceholder: semanticColors.contentTertiary, + inputPlaceholderDisabled: semanticColors.contentStateDisabled, // Menu - menuFill: themePrimitives.mono600, + menuFill: semanticColors.backgroundPrimary, - menuFillHover: themePrimitives.mono700, - menuFontDefault: themePrimitives.mono300, - menuFontDisabled: themePrimitives.mono400, - menuFontHighlighted: themePrimitives.white, - menuFontSelected: themePrimitives.white, + menuFillHover: semanticColors.backgroundSecondary, + menuFontDefault: semanticColors.contentPrimary, + menuFontDisabled: semanticColors.contentStateDisabled, + menuFontHighlighted: semanticColors.contentPrimary, + menuFontSelected: semanticColors.contentPrimary, // Tab - tabBarFill: themePrimitives.mono1000, + tabBarFill: semanticColors.backgroundPrimary, - tabColor: themePrimitives.mono300, + tabColor: semanticColors.contentTertiary, // Spinner - spinnerTrackFill: themePrimitives.mono100, + spinnerTrackFill: semanticColors.backgroundTertiary, // Progress bar - progressbarTrackFill: themePrimitives.mono100, + progressbarTrackFill: semanticColors.backgroundTertiary, // Tooltip - tooltipBackground: themePrimitives.mono200, + tooltipBackground: semanticColors.backgroundInverseSecondary, + + tooltipText: semanticColors.contentInversePrimary, - tooltipText: themePrimitives.mono1000, + // Rating + ratingInactiveFill: primitiveDarkColors.gray500Dark, + ratingStroke: primitiveDarkColors.gray700Dark, }); diff --git a/src/themes/dark-theme/color-tokens.ts b/src/themes/dark-theme/color-foundation-tokens.ts similarity index 77% rename from src/themes/dark-theme/color-tokens.ts rename to src/themes/dark-theme/color-foundation-tokens.ts index 31ebc0d0b6..9c9ec444bd 100644 --- a/src/themes/dark-theme/color-tokens.ts +++ b/src/themes/dark-theme/color-foundation-tokens.ts @@ -4,15 +4,15 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import { colors } from '../../tokens'; -import type { FoundationColorTokens } from '../types'; +import { primitiveDarkColors } from '../../tokens'; +import type { FoundationColors } from '../types'; // color constants -export const darkColorTokens: FoundationColorTokens = { +export const foundationColors: FoundationColors = { // Primary Palette - primaryA: colors.gray200, - primaryB: colors.gray900, - primary: colors.white, + primaryA: primitiveDarkColors.gray900Dark, + primaryB: primitiveDarkColors.gray50Dark, + primary: '#FFFFFF', primary50: '#F6F6F6', primary100: '#EEEEEE', primary200: '#E2E2E2', @@ -22,7 +22,7 @@ export const darkColorTokens: FoundationColorTokens = { primary600: '#545454', primary700: '#333333', // Accent Palette - accent: colors.blue400, + accent: primitiveDarkColors.blue400Dark, accent50: '#EFF3FE', accent100: '#D4E2FC', accent200: '#A0BFF8', @@ -32,7 +32,7 @@ export const darkColorTokens: FoundationColorTokens = { accent600: '#174291', accent700: '#102C60', // Negative Palette - negative: colors.red500, + negative: primitiveDarkColors.red400Dark, negative50: '#FFEFED', negative100: '#FED7D2', negative200: '#F1998E', @@ -42,7 +42,7 @@ export const darkColorTokens: FoundationColorTokens = { negative600: '#870F00', negative700: '#5A0A00', // Warning Palette - warning: colors.yellow500, + warning: primitiveDarkColors.yellow400Dark, warning50: '#FFFAF0', warning100: '#FFF2D9', warning200: '#FFE3AC', @@ -52,7 +52,7 @@ export const darkColorTokens: FoundationColorTokens = { warning600: '#996F00', warning700: '#674D1B', // Positive Palette - positive: colors.green400, + positive: primitiveDarkColors.green400Dark, positive50: '#E6F2ED', positive100: '#ADDEC9', positive200: '#66D19E', @@ -75,9 +75,6 @@ export const darkColorTokens: FoundationColorTokens = { mono800: '#141414', mono900: '#111111', mono1000: '#000000', - // Rating Palette, - ratingInactiveFill: colors.gray500, - ratingStroke: colors.gray700, }; -export default darkColorTokens; +export default foundationColors; diff --git a/src/themes/dark-theme/color-semantic-tokens.ts b/src/themes/dark-theme/color-semantic-tokens.ts index ef74b65a4e..b8a5ee59f5 100644 --- a/src/themes/dark-theme/color-semantic-tokens.ts +++ b/src/themes/dark-theme/color-semantic-tokens.ts @@ -5,97 +5,90 @@ This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ import type { - FoundationColorTokens, - CoreSemanticColorTokens, - CoreExtensionSemanticColorTokens, - DeprecatedSemanticColorTokens, - SemanticColorTokens, + FoundationColors, + CoreSemanticColors, + CoreExtensionSemanticColors, + DeprecatedSemanticColors, + SemanticColors, } from '../types'; -import defaultFoundationColorTokens from './color-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; import { hexToRgb as hexToRgba } from '../../styles/util'; -import colors from '../../tokens/colors'; +import { primitiveLightColors, primitiveDarkColors } from '../../tokens/color-primitive-tokens'; -export default ( - // themePrimitives or foundation colors - foundation: FoundationColorTokens = defaultFoundationColorTokens -): SemanticColorTokens => { - const core: CoreSemanticColorTokens = { +export default (foundation: FoundationColors = defaultFoundationColors): SemanticColors => { + const core: CoreSemanticColors = { // Background backgroundPrimary: foundation.primaryB, - backgroundSecondary: colors.gray800, - backgroundTertiary: colors.gray700, - backgroundInversePrimary: foundation.primaryA, - backgroundInverseSecondary: colors.gray300, + backgroundSecondary: primitiveDarkColors.gray100Dark, + backgroundTertiary: primitiveDarkColors.gray200Dark, + backgroundInversePrimary: primitiveDarkColors.gray800Dark, + backgroundInverseSecondary: primitiveDarkColors.gray700Dark, // Content - contentPrimary: colors.white, - contentSecondary: colors.gray300, - contentTertiary: colors.gray400, - contentInversePrimary: colors.black, - contentInverseSecondary: colors.gray700, - contentInverseTertiary: colors.gray600, + contentPrimary: foundation.primaryA, + contentSecondary: primitiveDarkColors.gray800Dark, + contentTertiary: primitiveDarkColors.gray700Dark, + contentInversePrimary: primitiveDarkColors.black, + contentInverseSecondary: primitiveDarkColors.gray200Dark, + contentInverseTertiary: primitiveDarkColors.gray300Dark, // Border - borderOpaque: colors.gray700, - // @ts-ignore - borderTransparent: hexToRgba(foundation.primaryA, '0.08'), + borderOpaque: primitiveDarkColors.gray100Dark, + borderTransparent: hexToRgba(foundation.primaryA, '0.08') || '', borderSelected: foundation.primaryA, - borderInverseOpaque: colors.gray400, - // @ts-ignore - borderInverseTransparent: hexToRgba(foundation.primaryB, '0.2'), + borderInverseOpaque: primitiveDarkColors.gray300Dark, + borderInverseTransparent: hexToRgba(foundation.primaryB, '0.2') || '', borderInverseSelected: foundation.primaryB, }; - const coreExtensions: CoreExtensionSemanticColorTokens = { + const coreExtensions: CoreExtensionSemanticColors = { // Backgrounds - backgroundStateDisabled: colors.gray800, - // @ts-ignore - backgroundOverlay: hexToRgba(colors.black, '0.7'), - // @ts-ignore - backgroundOverlayArt: hexToRgba(colors.black, '0.16'), + backgroundStateDisabled: primitiveDarkColors.gray100Dark, + backgroundOverlay: hexToRgba(primitiveDarkColors.black, '0.7') || '', + backgroundOverlayArt: hexToRgba(primitiveDarkColors.black, '0.16') || '', backgroundAccent: foundation.accent, backgroundNegative: foundation.negative, backgroundWarning: foundation.warning, - backgroundPositive: colors.green500, - backgroundAccentLight: colors.blue700, - backgroundPositiveLight: colors.green700, - backgroundNegativeLight: colors.red700, - backgroundWarningLight: colors.yellow700, - backgroundAlwaysDark: colors.gray900, - backgroundAlwaysLight: colors.gray100, + backgroundPositive: foundation.positive, + backgroundAccentLight: primitiveDarkColors.blue100Dark, + backgroundPositiveLight: primitiveDarkColors.green100Dark, + backgroundNegativeLight: primitiveDarkColors.red100Dark, + backgroundWarningLight: primitiveDarkColors.yellow100Dark, + backgroundAlwaysDark: primitiveDarkColors.gray100Dark, + backgroundAlwaysLight: primitiveDarkColors.gray900Dark, // Content - contentStateDisabled: colors.gray600, - contentAccent: colors.blue300, - contentOnColor: colors.white, - contentOnColorInverse: colors.black, - contentNegative: colors.red300, - contentWarning: colors.yellow300, - contentPositive: colors.green300, + contentStateDisabled: primitiveDarkColors.gray400Dark, + contentAccent: primitiveDarkColors.blue600Dark, + contentOnColor: primitiveDarkColors.gray900Dark, + contentOnColorInverse: primitiveDarkColors.black, + contentNegative: primitiveDarkColors.red600Dark, + contentWarning: primitiveDarkColors.yellow600Dark, + contentPositive: primitiveDarkColors.green600Dark, // Border - borderStateDisabled: colors.gray800, - borderAccent: colors.blue400, - borderAccentLight: colors.blue500, - borderNegative: colors.red500, - borderWarning: colors.yellow500, - borderPositive: colors.green500, - borderNegativeLight: colors.red200, - borderWarningLight: colors.yellow200, - borderPositiveLight: colors.green200, + borderStateDisabled: primitiveDarkColors.gray100Dark, + borderAccent: primitiveDarkColors.blue500Dark, + borderAccentLight: primitiveDarkColors.blue400Dark, + borderNegative: primitiveDarkColors.red500Dark, + borderNegativeLight: primitiveDarkColors.red400Dark, + borderWarning: primitiveDarkColors.yellow500Dark, + borderWarningLight: primitiveDarkColors.yellow400Dark, + borderPositive: primitiveDarkColors.green500Dark, + borderPositiveLight: primitiveDarkColors.green400Dark, // Programs - safety: colors.blue400, - eatsGreen400: colors.green400, - freightBlue400: colors.cobalt400, - jumpRed400: colors.red400, - rewardsTier1: colors.blue400, - rewardsTier2: colors.yellow400, - rewardsTier3: colors.platinum400, - rewardsTier4: colors.gray200, - membership: colors.yellow600, + safety: primitiveLightColors.blue600, + eatsGreen400: primitiveLightColors.green600, + freightBlue400: primitiveLightColors.cobalt400, + rewardsTier1: primitiveLightColors.blue600, + rewardsTier2: primitiveLightColors.yellow300, + rewardsTier3: primitiveLightColors.platinum400, + rewardsTier4: primitiveLightColors.black, + membership: primitiveLightColors.yellow600, }; - const deprecated: DeprecatedSemanticColorTokens = { + const deprecated: DeprecatedSemanticColors = { + jumpRed400: primitiveLightColors.red600, backgroundOverlayLight: coreExtensions.backgroundOverlay, backgroundOverlayDark: coreExtensions.backgroundOverlay, backgroundLightAccent: coreExtensions.backgroundAccentLight, diff --git a/src/themes/dark-theme/create-dark-theme.ts b/src/themes/dark-theme/create-dark-theme.ts index 1f07baa6bd..5d74426d08 100644 --- a/src/themes/dark-theme/create-dark-theme.ts +++ b/src/themes/dark-theme/create-dark-theme.ts @@ -4,53 +4,33 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import animation from '../shared/animation'; -import borders from './borders'; -import breakpoints from '../shared/breakpoints'; import deepMerge from '../../utils/deep-merge'; -import { getFoundationColorTokenOverrides } from '../utils'; -import defaultFoundationColorTokens from './color-tokens'; -import { colors as primitiveColorTokens } from '../../tokens'; -import getComponentColorTokens from './color-component-tokens'; -import getSemanticColorTokens from './color-semantic-tokens'; -import typography from '../shared/typography'; -import grid from '../shared/grid'; -import lighting from '../shared/lighting'; -import mediaQuery from '../shared/media-query'; -import sizing from '../shared/sizing'; +import { getFoundationColorOverrides } from '../utils'; +import { primitiveColors } from '../../tokens'; +import getComponentColors from './color-component-tokens'; +import getSemanticColors from './color-semantic-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; +import { DarkTheme } from './dark-theme'; -import type { Theme, MakeExtendable, DeepPartial } from '../../styles/types'; +import type { DeepPartial, MakeExtendable, Theme } from '../../styles/types'; export default function createDarkTheme<OverridesT extends DeepPartial<MakeExtendable<Theme>> = {}>( overrides?: OverridesT ): Theme & OverridesT { - const foundationColorTokens = { - ...defaultFoundationColorTokens, - ...getFoundationColorTokenOverrides(overrides?.colors), + const foundationColors = { + ...defaultFoundationColors, + ...getFoundationColorOverrides(overrides?.colors), }; - const semanticColorTokens = getSemanticColorTokens(foundationColorTokens); - const componentColorTokens = getComponentColorTokens(foundationColorTokens); + const semanticColors = getSemanticColors(foundationColors); + const componentColors = getComponentColors(semanticColors); const theme = { - animation, - borders, - breakpoints, + ...structuredClone(DarkTheme), colors: { - ...primitiveColorTokens, - ...foundationColorTokens, - ...semanticColorTokens, - ...componentColorTokens, - }, - direction: 'auto', - grid, - lighting, - mediaQuery, - sizing, - typography, - // TODO(#2318) Remove in v11, the next major version. - // Do not use. - zIndex: { - modal: 2000, + ...primitiveColors, + ...foundationColors, + ...semanticColors, + ...componentColors, }, }; diff --git a/src/themes/dark-theme/dark-theme.ts b/src/themes/dark-theme/dark-theme.ts index 89e2526fa3..dbb3c23622 100644 --- a/src/themes/dark-theme/dark-theme.ts +++ b/src/themes/dark-theme/dark-theme.ts @@ -4,10 +4,10 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import foundationColorTokens from './color-tokens'; -import primitiveColorTokens from '../../tokens/colors'; -import getSemanticColorTokens from './color-semantic-tokens'; -import getComponentColorTokens from './color-component-tokens'; +import foundationColors from './color-foundation-tokens'; +import primitiveColors from '../../tokens/color-primitive-tokens'; +import getSemanticColors from './color-semantic-tokens'; +import getComponentColors from './color-component-tokens'; import borders from './borders'; import lighting from '../shared/lighting'; import typography from '../shared/typography'; @@ -22,10 +22,10 @@ import type { Theme } from '../../styles/types'; export const DarkTheme: Theme = { name: 'dark-theme', colors: { - ...foundationColorTokens, - ...primitiveColorTokens, - ...getComponentColorTokens(), - ...getSemanticColorTokens(), + ...foundationColors, + ...primitiveColors, + ...getComponentColors(), + ...getSemanticColors(), }, animation, breakpoints, diff --git a/src/themes/dark-theme/primitives.ts b/src/themes/dark-theme/primitives.ts index 8e54e5b39a..f5a5b19cd6 100644 --- a/src/themes/dark-theme/primitives.ts +++ b/src/themes/dark-theme/primitives.ts @@ -4,14 +4,14 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import defaultFoundationColorTokens from './color-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; import { fontTokens } from '../shared/typography'; import type { Primitives } from '../types'; // We don't use this ourselves. We provide it for backward compatibility. // People may have used it to create their own custom theme. const primitives: Primitives = { - ...defaultFoundationColorTokens, + ...defaultFoundationColors, ...fontTokens, }; diff --git a/src/themes/light-theme/__tests__/create-light-theme.test.ts b/src/themes/light-theme/__tests__/create-light-theme.test.ts new file mode 100644 index 0000000000..133878c2b5 --- /dev/null +++ b/src/themes/light-theme/__tests__/create-light-theme.test.ts @@ -0,0 +1,12 @@ +import { LightTheme } from '../light-theme'; +import createLightTheme from '../create-light-theme'; + +describe('createLightTheme', () => { + test('without any overrides, returns the same value as LightTheme variable', () => { + expect(createLightTheme()).toEqual(LightTheme); + }); + + test('ensure property `name` is populated', () => { + expect(createLightTheme().name).toEqual('light-theme'); + }); +}); diff --git a/src/themes/light-theme/color-component-tokens.ts b/src/themes/light-theme/color-component-tokens.ts index 8bd4f2940c..dfb2f0006a 100644 --- a/src/themes/light-theme/color-component-tokens.ts +++ b/src/themes/light-theme/color-component-tokens.ts @@ -4,413 +4,419 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import defaultFoundationColorTokens from './color-tokens'; -import type { FoundationColorTokens, ComponentColorTokens } from '../types'; +import type { SemanticColors, ComponentColors } from '../types'; +import getSemanticColors from './color-semantic-tokens'; +import { primitiveLightColors } from '../../tokens'; + +const defaultSemanticColors = getSemanticColors(); const tagHoverBackground = `rgba(0, 0, 0, 0.08)`; /* ---- Component colors ---- */ -// TODO(#2318) Make it a plain object in the next v11 major version -// with values taken from `defaultFoundationColorTokens`. -// Due to the legacy `createTheme` type the value need to be -// overrideable through primitives (`foundation` ) -export default ( - themePrimitives: FoundationColorTokens = defaultFoundationColorTokens -): ComponentColorTokens => ({ - bannerActionLowInfo: themePrimitives.accent100, - bannerActionLowNegative: themePrimitives.negative100, - bannerActionLowPositive: themePrimitives.positive100, - bannerActionLowWarning: themePrimitives.warning200, - bannerActionHighInfo: themePrimitives.accent500, - bannerActionHighNegative: themePrimitives.negative500, - bannerActionHighPositive: themePrimitives.positive500, - bannerActionHighWarning: themePrimitives.warning200, +export default (semanticColors: SemanticColors = defaultSemanticColors): ComponentColors => ({ + // Banner + bannerActionLowInfo: primitiveLightColors.blue100, + bannerActionLowNegative: primitiveLightColors.red100, + bannerActionLowPositive: primitiveLightColors.green100, + bannerActionLowWarning: primitiveLightColors.yellow100, + bannerActionHighInfo: primitiveLightColors.blue700, + bannerActionHighNegative: primitiveLightColors.red700, + bannerActionHighPositive: primitiveLightColors.green700, + bannerActionHighWarning: primitiveLightColors.yellow200, + + // BottomNavigation + bottomNavigationText: primitiveLightColors.gray600, + bottomNavigationSelectedText: semanticColors.contentPrimary, // Buttons - buttonPrimaryFill: themePrimitives.primary, - - buttonPrimaryText: themePrimitives.white, - buttonPrimaryHover: themePrimitives.primary700, - buttonPrimaryActive: themePrimitives.primary600, - buttonPrimarySelectedFill: themePrimitives.primary600, - buttonPrimarySelectedText: themePrimitives.white, - buttonPrimarySpinnerForeground: themePrimitives.accent, - buttonPrimarySpinnerBackground: themePrimitives.primaryB, - buttonSecondaryFill: themePrimitives.primary100, - buttonSecondaryText: themePrimitives.primary, - buttonSecondaryHover: themePrimitives.primary200, - buttonSecondaryActive: themePrimitives.primary300, - buttonSecondarySelectedFill: themePrimitives.primary300, - buttonSecondarySelectedText: themePrimitives.primary, - buttonSecondarySpinnerForeground: themePrimitives.accent, - buttonSecondarySpinnerBackground: themePrimitives.primaryB, + buttonPrimaryFill: semanticColors.backgroundInversePrimary, + + buttonPrimaryText: semanticColors.contentInversePrimary, + buttonPrimaryHover: primitiveLightColors.gray900, + buttonPrimaryActive: primitiveLightColors.gray800, + buttonPrimarySelectedFill: semanticColors.backgroundInversePrimary, + buttonPrimarySelectedText: semanticColors.contentInversePrimary, + buttonPrimarySpinnerForeground: semanticColors.backgroundAccent, + buttonPrimarySpinnerBackground: semanticColors.backgroundPrimary, + buttonSecondaryFill: semanticColors.backgroundSecondary, + buttonSecondaryText: semanticColors.contentPrimary, + buttonSecondaryHover: primitiveLightColors.gray200, + buttonSecondaryActive: primitiveLightColors.gray300, + buttonSecondarySelectedFill: semanticColors.backgroundInversePrimary, + buttonSecondarySelectedText: semanticColors.contentInversePrimary, + buttonSecondarySpinnerForeground: semanticColors.backgroundAccent, + buttonSecondarySpinnerBackground: semanticColors.backgroundPrimary, buttonTertiaryFill: 'transparent', - buttonTertiaryText: themePrimitives.primary, - buttonTertiaryHover: themePrimitives.primary50, - buttonTertiaryActive: themePrimitives.primary100, - buttonTertiarySelectedFill: themePrimitives.primary100, - buttonTertiarySelectedText: themePrimitives.primary, - buttonTertiaryDisabledActiveFill: themePrimitives.primary50, - buttonTertiaryDisabledActiveText: themePrimitives.mono600, - buttonTertiarySpinnerForeground: themePrimitives.accent, - buttonTertiarySpinnerBackground: themePrimitives.primary100, - buttonDisabledFill: themePrimitives.mono200, - buttonDisabledText: themePrimitives.mono600, - buttonDisabledActiveFill: themePrimitives.mono700, - buttonDisabledActiveText: themePrimitives.mono100, - buttonDisabledSpinnerForeground: themePrimitives.mono600, - buttonDisabledSpinnerBackground: themePrimitives.mono400, + buttonTertiaryText: semanticColors.contentPrimary, + buttonTertiaryHover: primitiveLightColors.gray50, + buttonTertiaryActive: primitiveLightColors.gray100, + buttonTertiarySelectedFill: semanticColors.backgroundInversePrimary, + buttonTertiarySelectedText: semanticColors.contentInversePrimary, + buttonTertiaryDisabledActiveFill: semanticColors.backgroundStateDisabled, + buttonTertiaryDisabledActiveText: semanticColors.contentStateDisabled, + buttonTertiarySpinnerForeground: semanticColors.backgroundAccent, + buttonTertiarySpinnerBackground: semanticColors.backgroundTertiary, + buttonDisabledFill: semanticColors.backgroundStateDisabled, + buttonDisabledText: semanticColors.contentStateDisabled, + buttonDisabledActiveFill: semanticColors.backgroundStateDisabled, + buttonDisabledActiveText: semanticColors.contentStateDisabled, + buttonDisabledSpinnerForeground: semanticColors.contentStateDisabled, + buttonDisabledSpinnerBackground: semanticColors.backgroundPrimary, // Breadcrumbs - breadcrumbsText: themePrimitives.black, + breadcrumbsText: semanticColors.contentPrimary, - breadcrumbsSeparatorFill: themePrimitives.mono700, + breadcrumbsSeparatorFill: semanticColors.contentTertiary, // Datepicker - calendarBackground: themePrimitives.mono100, - - calendarForeground: themePrimitives.mono1000, - calendarForegroundDisabled: themePrimitives.mono500, - calendarHeaderBackground: themePrimitives.white, - calendarHeaderForeground: themePrimitives.primary, - calendarHeaderBackgroundActive: themePrimitives.primary700, - calendarHeaderForegroundDisabled: themePrimitives.primary500, - calendarDayForegroundPseudoSelected: themePrimitives.mono1000, - calendarDayBackgroundPseudoSelectedHighlighted: themePrimitives.primary200, - calendarDayForegroundPseudoSelectedHighlighted: themePrimitives.mono1000, - calendarDayBackgroundSelected: themePrimitives.primary, - calendarDayForegroundSelected: themePrimitives.white, - calendarDayBackgroundSelectedHighlighted: themePrimitives.primary, - calendarDayForegroundSelectedHighlighted: themePrimitives.white, + calendarBackground: semanticColors.backgroundPrimary, + + calendarForeground: semanticColors.contentPrimary, + calendarForegroundDisabled: semanticColors.contentStateDisabled, + calendarHeaderBackground: semanticColors.backgroundPrimary, + calendarHeaderForeground: semanticColors.contentPrimary, + calendarHeaderBackgroundActive: semanticColors.backgroundInversePrimary, + calendarHeaderForegroundDisabled: semanticColors.contentStateDisabled, + calendarDayForegroundPseudoSelected: semanticColors.backgroundInversePrimary, + calendarDayBackgroundPseudoSelectedHighlighted: semanticColors.backgroundTertiary, + calendarDayForegroundPseudoSelectedHighlighted: semanticColors.contentPrimary, + calendarDayBackgroundSelected: semanticColors.backgroundInversePrimary, + calendarDayForegroundSelected: semanticColors.contentInversePrimary, + calendarDayBackgroundSelectedHighlighted: semanticColors.backgroundInversePrimary, + calendarDayForegroundSelectedHighlighted: semanticColors.contentInversePrimary, // Combobox - comboboxListItemFocus: themePrimitives.mono200, + comboboxListItemFocus: semanticColors.backgroundSecondary, - comboboxListItemHover: themePrimitives.mono300, + comboboxListItemHover: semanticColors.backgroundTertiary, // FileUploader - fileUploaderBackgroundColor: themePrimitives.mono200, + fileUploaderBackgroundColor: semanticColors.backgroundSecondary, - fileUploaderBackgroundColorActive: themePrimitives.primary50, - fileUploaderBorderColorActive: themePrimitives.primary, - fileUploaderBorderColorDefault: themePrimitives.mono500, - fileUploaderMessageColor: themePrimitives.mono800, + fileUploaderBackgroundColorActive: semanticColors.backgroundPrimary, + fileUploaderBorderColorActive: semanticColors.borderSelected, + fileUploaderBorderColorDefault: semanticColors.borderOpaque, + fileUploaderMessageColor: semanticColors.contentPrimary, // Links - linkText: themePrimitives.primary, + linkText: semanticColors.contentPrimary, - linkVisited: themePrimitives.primary700, - linkHover: themePrimitives.primary600, - linkActive: themePrimitives.primary500, + linkVisited: primitiveLightColors.gray600, + linkHover: primitiveLightColors.gray800, + linkActive: primitiveLightColors.gray700, // List - listHeaderFill: themePrimitives.white, + listHeaderFill: semanticColors.backgroundPrimary, - listBodyFill: themePrimitives.white, + listBodyFill: semanticColors.backgroundPrimary, // ProgressSteps - progressStepsCompletedText: themePrimitives.white, + progressStepsCompletedText: semanticColors.contentInversePrimary, - progressStepsCompletedFill: themePrimitives.primary, - progressStepsActiveText: themePrimitives.white, - progressStepsActiveFill: themePrimitives.primary, + progressStepsCompletedFill: semanticColors.backgroundInversePrimary, + progressStepsActiveText: semanticColors.contentInversePrimary, + progressStepsActiveFill: semanticColors.backgroundInversePrimary, // Toggle - toggleFill: themePrimitives.white, + toggleFill: semanticColors.backgroundPrimary, - toggleFillChecked: themePrimitives.primary, - toggleFillDisabled: themePrimitives.mono600, - toggleTrackFill: themePrimitives.mono400, - toggleTrackFillDisabled: themePrimitives.mono300, + toggleFillChecked: semanticColors.contentPrimary, + toggleFillDisabled: semanticColors.contentStateDisabled, + toggleTrackFill: semanticColors.backgroundTertiary, + toggleTrackFillDisabled: semanticColors.backgroundStateDisabled, // Tick - tickFill: themePrimitives.mono100, - - tickFillHover: themePrimitives.mono200, - tickFillActive: themePrimitives.mono300, - tickFillSelected: themePrimitives.primary, - tickFillSelectedHover: themePrimitives.primary700, - tickFillSelectedHoverActive: themePrimitives.primary600, - tickFillError: themePrimitives.negative50, - tickFillErrorHover: themePrimitives.negative100, - tickFillErrorHoverActive: themePrimitives.negative200, - tickFillErrorSelected: themePrimitives.negative400, - tickFillErrorSelectedHover: themePrimitives.negative500, - tickFillErrorSelectedHoverActive: themePrimitives.negative600, - tickFillDisabled: themePrimitives.mono600, - tickBorder: themePrimitives.mono700, - tickBorderError: themePrimitives.negative400, - tickMarkFill: themePrimitives.white, - tickMarkFillError: themePrimitives.white, - tickMarkFillDisabled: themePrimitives.mono100, + tickFill: semanticColors.backgroundPrimary, + + tickFillHover: primitiveLightColors.gray50, + tickFillActive: primitiveLightColors.gray100, + tickFillSelected: semanticColors.contentPrimary, + tickFillSelectedHover: primitiveLightColors.gray900, + tickFillSelectedHoverActive: primitiveLightColors.gray800, + tickFillError: semanticColors.backgroundPrimary, + tickFillErrorHover: primitiveLightColors.gray50, + tickFillErrorHoverActive: primitiveLightColors.gray100, + tickFillErrorSelected: semanticColors.contentNegative, + tickFillErrorSelectedHover: primitiveLightColors.red700, + tickFillErrorSelectedHoverActive: primitiveLightColors.red800, + tickFillDisabled: semanticColors.backgroundStateDisabled, + tickBorder: semanticColors.contentTertiary, + tickBorderError: semanticColors.borderNegative, + tickMarkFill: semanticColors.contentInversePrimary, + tickMarkFillError: semanticColors.contentOnColor, + tickMarkFillDisabled: semanticColors.contentInversePrimary, // Slider/Toggle sliderTrackFill: 'transparent', - sliderHandleFill: themePrimitives.primaryA, - sliderHandleFillDisabled: themePrimitives.primary400, - sliderHandleInnerFill: themePrimitives.primaryA, - sliderTrackFillHover: themePrimitives.mono500, - sliderTrackFillActive: themePrimitives.mono600, - sliderTrackFillDisabled: themePrimitives.mono300, - sliderHandleInnerFillDisabled: themePrimitives.mono400, - sliderHandleInnerFillSelectedHover: themePrimitives.primary, - sliderHandleInnerFillSelectedActive: themePrimitives.primary500, + sliderHandleFill: semanticColors.contentPrimary, + sliderHandleFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFill: semanticColors.contentPrimary, + sliderTrackFillHover: primitiveLightColors.gray200, + sliderTrackFillActive: primitiveLightColors.gray300, + sliderTrackFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFillDisabled: semanticColors.backgroundStateDisabled, + sliderHandleInnerFillSelectedHover: primitiveLightColors.gray900, + sliderHandleInnerFillSelectedActive: primitiveLightColors.gray800, // Inputs - inputBorder: themePrimitives.mono300, - - inputFill: themePrimitives.mono300, - inputFillError: themePrimitives.negative50, - inputFillDisabled: themePrimitives.mono200, - inputFillActive: themePrimitives.mono200, - inputFillPositive: themePrimitives.positive50, - inputTextDisabled: themePrimitives.mono600, - inputBorderError: themePrimitives.negative200, - inputBorderPositive: themePrimitives.positive200, - inputEnhancerFill: themePrimitives.mono300, - inputEnhancerFillDisabled: themePrimitives.mono300, - inputEnhancerTextDisabled: themePrimitives.mono600, - inputPlaceholder: themePrimitives.mono700, - inputPlaceholderDisabled: themePrimitives.mono600, + inputBorder: semanticColors.borderOpaque, + + inputFill: semanticColors.backgroundSecondary, + inputFillError: semanticColors.backgroundPrimary, + inputFillDisabled: semanticColors.backgroundStateDisabled, + inputFillActive: semanticColors.backgroundPrimary, + inputFillPositive: semanticColors.backgroundPrimary, + inputTextDisabled: semanticColors.contentStateDisabled, + inputBorderError: semanticColors.borderNegative, + inputBorderPositive: semanticColors.borderPositive, + inputEnhancerFill: semanticColors.contentPrimary, + inputEnhancerFillDisabled: semanticColors.contentStateDisabled, + inputEnhancerTextDisabled: semanticColors.contentStateDisabled, + inputPlaceholder: semanticColors.contentTertiary, + inputPlaceholderDisabled: semanticColors.contentStateDisabled, // Menu - menuFill: themePrimitives.mono100, + menuFill: semanticColors.backgroundPrimary, - menuFillHover: themePrimitives.mono200, - menuFontDefault: themePrimitives.mono800, - menuFontDisabled: themePrimitives.mono500, - menuFontHighlighted: themePrimitives.mono1000, - menuFontSelected: themePrimitives.mono1000, + menuFillHover: semanticColors.backgroundSecondary, + menuFontDefault: semanticColors.contentPrimary, + menuFontDisabled: semanticColors.contentStateDisabled, + menuFontHighlighted: semanticColors.contentPrimary, + menuFontSelected: semanticColors.contentPrimary, // Modal - modalCloseColor: themePrimitives.mono1000, + modalCloseColor: semanticColors.contentPrimary, - modalCloseColorHover: themePrimitives.mono800, - modalCloseColorFocus: themePrimitives.mono800, + modalCloseColorHover: primitiveLightColors.gray900, + modalCloseColorFocus: primitiveLightColors.gray800, // Tab - tabBarFill: themePrimitives.mono200, + tabBarFill: semanticColors.backgroundPrimary, - tabColor: themePrimitives.mono800, + tabColor: semanticColors.contentTertiary, // Notification - notificationInfoBackground: themePrimitives.accent50, + notificationInfoBackground: semanticColors.backgroundAccentLight, - notificationInfoText: themePrimitives.primaryA, - notificationPositiveBackground: themePrimitives.positive50, - notificationPositiveText: themePrimitives.primaryA, - notificationWarningBackground: themePrimitives.warning50, - notificationWarningText: themePrimitives.primaryA, - notificationNegativeBackground: themePrimitives.negative50, - notificationNegativeText: themePrimitives.primaryA, + notificationInfoText: semanticColors.contentPrimary, + notificationPositiveBackground: semanticColors.backgroundPositiveLight, + notificationPositiveText: semanticColors.contentPrimary, + notificationWarningBackground: semanticColors.backgroundWarningLight, + notificationWarningText: semanticColors.contentPrimary, + notificationNegativeBackground: semanticColors.backgroundNegativeLight, + notificationNegativeText: semanticColors.contentPrimary, // Tag // Custom ramps - tagFontDisabledRampUnit: '100', + tagFontDisabledRampUnit: '200', tagSolidFontRampUnit: '0', tagSolidRampUnit: '400', - tagOutlinedFontRampUnit: '400', - tagOutlinedRampUnit: '200', + tagOutlinedFontRampUnit: '600', + tagOutlinedRampUnit: '600', // Deprecated tagSolidHoverRampUnit: '50', tagSolidActiveRampUnit: '100', tagSolidDisabledRampUnit: '50', - tagSolidFontHoverRampUnit: '500', + tagSolidFontHoverRampUnit: '700', tagLightRampUnit: '50', tagLightHoverRampUnit: '100', - tagLightActiveRampUnit: '100', - tagLightFontRampUnit: '500', - tagLightFontHoverRampUnit: '500', - tagOutlinedHoverRampUnit: '50', - tagOutlinedActiveRampUnit: '0', - tagOutlinedFontHoverRampUnit: '400', + tagLightActiveRampUnit: '200', + tagLightFontRampUnit: '600', + tagLightFontHoverRampUnit: '200', + tagOutlinedHoverRampUnit: '700', + tagOutlinedActiveRampUnit: '800', + tagOutlinedFontHoverRampUnit: '700', // Neutral - tagNeutralFontDisabled: themePrimitives.mono600, + tagNeutralFontDisabled: primitiveLightColors.gray200, - tagNeutralOutlinedDisabled: themePrimitives.mono400, - tagNeutralSolidFont: themePrimitives.white, - tagNeutralSolidBackground: themePrimitives.black, - tagNeutralOutlinedBackground: themePrimitives.mono600, - tagNeutralOutlinedFont: themePrimitives.black, + tagNeutralOutlinedDisabled: primitiveLightColors.gray200, + tagNeutralSolidFont: primitiveLightColors.white, + tagNeutralSolidBackground: primitiveLightColors.gray600, + tagNeutralOutlinedBackground: primitiveLightColors.gray50, + tagNeutralOutlinedFont: primitiveLightColors.gray700, // Deprecated - tagNeutralSolidHover: themePrimitives.mono300, - - tagNeutralSolidActive: themePrimitives.mono400, - tagNeutralSolidDisabled: themePrimitives.mono200, - tagNeutralSolidFontHover: themePrimitives.mono900, - tagNeutralLightBackground: themePrimitives.mono300, - tagNeutralLightHover: themePrimitives.mono300, - tagNeutralLightActive: themePrimitives.mono400, - tagNeutralLightDisabled: themePrimitives.mono200, - tagNeutralLightFont: themePrimitives.mono900, - tagNeutralLightFontHover: themePrimitives.mono900, - tagNeutralOutlinedActive: themePrimitives.mono900, - tagNeutralOutlinedFontHover: themePrimitives.mono800, + tagNeutralSolidHover: primitiveLightColors.gray900, + + tagNeutralSolidActive: primitiveLightColors.gray800, + tagNeutralSolidDisabled: primitiveLightColors.gray200, + tagNeutralSolidFontHover: primitiveLightColors.gray700, + tagNeutralLightBackground: primitiveLightColors.white, + tagNeutralLightHover: primitiveLightColors.gray50, + tagNeutralLightActive: primitiveLightColors.gray100, + tagNeutralLightDisabled: primitiveLightColors.gray200, + tagNeutralLightFont: primitiveLightColors.gray600, + tagNeutralLightFontHover: primitiveLightColors.gray700, + tagNeutralOutlinedActive: primitiveLightColors.gray800, + tagNeutralOutlinedFontHover: primitiveLightColors.gray700, tagNeutralOutlinedHover: tagHoverBackground, // Primary - tagPrimaryFontDisabled: themePrimitives.primary400, + tagPrimaryFontDisabled: primitiveLightColors.gray300, - tagPrimaryOutlinedDisabled: themePrimitives.primary200, - tagPrimarySolidFont: themePrimitives.white, - tagPrimarySolidBackground: themePrimitives.primary, - tagPrimaryOutlinedFontHover: themePrimitives.primary, - tagPrimaryOutlinedFont: themePrimitives.primary, + tagPrimaryOutlinedDisabled: primitiveLightColors.gray200, + tagPrimarySolidFont: primitiveLightColors.white, + tagPrimarySolidBackground: primitiveLightColors.gray600, + tagPrimaryOutlinedFontHover: primitiveLightColors.gray900, + tagPrimaryOutlinedFont: primitiveLightColors.gray700, // Deprecated - tagPrimarySolidHover: themePrimitives.primary100, - - tagPrimarySolidActive: themePrimitives.primary200, - tagPrimarySolidDisabled: themePrimitives.primary50, - tagPrimarySolidFontHover: themePrimitives.primary700, - tagPrimaryLightBackground: themePrimitives.primary50, - tagPrimaryLightHover: themePrimitives.primary100, - tagPrimaryLightActive: themePrimitives.primary100, - tagPrimaryLightDisabled: themePrimitives.primary50, - tagPrimaryLightFont: themePrimitives.primary500, - tagPrimaryLightFontHover: themePrimitives.primary500, - tagPrimaryOutlinedActive: themePrimitives.primary600, + tagPrimarySolidHover: primitiveLightColors.gray900, + + tagPrimarySolidActive: primitiveLightColors.gray900, + tagPrimarySolidDisabled: primitiveLightColors.gray200, + tagPrimarySolidFontHover: primitiveLightColors.gray900, + tagPrimaryLightBackground: primitiveLightColors.white, + tagPrimaryLightHover: primitiveLightColors.gray50, + tagPrimaryLightActive: primitiveLightColors.gray100, + tagPrimaryLightDisabled: primitiveLightColors.gray200, + tagPrimaryLightFont: primitiveLightColors.black, + tagPrimaryLightFontHover: primitiveLightColors.gray900, + tagPrimaryOutlinedActive: primitiveLightColors.gray900, tagPrimaryOutlinedHover: tagHoverBackground, - tagPrimaryOutlinedBackground: themePrimitives.primary400, + tagPrimaryOutlinedBackground: primitiveLightColors.gray50, // Accent - tagAccentFontDisabled: themePrimitives.accent200, + tagAccentFontDisabled: primitiveLightColors.blue300, - tagAccentOutlinedDisabled: themePrimitives.accent200, - tagAccentSolidFont: themePrimitives.white, - tagAccentSolidBackground: themePrimitives.accent400, - tagAccentOutlinedBackground: themePrimitives.accent200, - tagAccentOutlinedFont: themePrimitives.accent400, + tagAccentOutlinedDisabled: primitiveLightColors.blue200, + tagAccentSolidFont: primitiveLightColors.white, + tagAccentSolidBackground: primitiveLightColors.blue600, + tagAccentOutlinedBackground: primitiveLightColors.blue50, + tagAccentOutlinedFont: primitiveLightColors.blue700, // Deprecated - tagAccentSolidHover: themePrimitives.accent50, - - tagAccentSolidActive: themePrimitives.accent100, - tagAccentSolidDisabled: themePrimitives.accent50, - tagAccentSolidFontHover: themePrimitives.accent500, - tagAccentLightBackground: themePrimitives.accent50, - tagAccentLightHover: themePrimitives.accent100, - tagAccentLightActive: themePrimitives.accent100, - tagAccentLightDisabled: themePrimitives.accent50, - tagAccentLightFont: themePrimitives.accent500, - tagAccentLightFontHover: themePrimitives.accent500, - tagAccentOutlinedActive: themePrimitives.accent600, - tagAccentOutlinedFontHover: themePrimitives.accent400, + tagAccentSolidHover: primitiveLightColors.blue50, + + tagAccentSolidActive: primitiveLightColors.blue100, + tagAccentSolidDisabled: primitiveLightColors.blue50, + tagAccentSolidFontHover: primitiveLightColors.blue500, + tagAccentLightBackground: primitiveLightColors.blue50, + tagAccentLightHover: primitiveLightColors.blue100, + tagAccentLightActive: primitiveLightColors.blue200, + tagAccentLightDisabled: primitiveLightColors.blue50, + tagAccentLightFont: primitiveLightColors.blue600, + tagAccentLightFontHover: primitiveLightColors.blue700, + tagAccentOutlinedActive: primitiveLightColors.blue800, + tagAccentOutlinedFontHover: primitiveLightColors.blue700, tagAccentOutlinedHover: tagHoverBackground, // Positive - tagPositiveFontDisabled: themePrimitives.positive200, + tagPositiveFontDisabled: primitiveLightColors.green300, - tagPositiveOutlinedDisabled: themePrimitives.positive200, - tagPositiveSolidFont: themePrimitives.white, - tagPositiveSolidBackground: themePrimitives.positive400, - tagPositiveOutlinedBackground: themePrimitives.positive200, - tagPositiveOutlinedFont: themePrimitives.positive400, + tagPositiveOutlinedDisabled: primitiveLightColors.green200, + tagPositiveSolidFont: primitiveLightColors.white, + tagPositiveSolidBackground: primitiveLightColors.green600, + tagPositiveOutlinedBackground: primitiveLightColors.green50, + tagPositiveOutlinedFont: primitiveLightColors.green700, // Deprecated - tagPositiveSolidHover: themePrimitives.positive50, - - tagPositiveSolidActive: themePrimitives.positive100, - tagPositiveSolidDisabled: themePrimitives.positive50, - tagPositiveSolidFontHover: themePrimitives.positive500, - tagPositiveLightBackground: themePrimitives.positive50, - tagPositiveLightHover: themePrimitives.positive100, - tagPositiveLightActive: themePrimitives.positive100, - tagPositiveLightDisabled: themePrimitives.positive50, - tagPositiveLightFont: themePrimitives.positive500, - tagPositiveLightFontHover: themePrimitives.positive500, - tagPositiveOutlinedActive: themePrimitives.positive600, - tagPositiveOutlinedFontHover: themePrimitives.positive400, + tagPositiveSolidHover: primitiveLightColors.green50, + + tagPositiveSolidActive: primitiveLightColors.green100, + tagPositiveSolidDisabled: primitiveLightColors.green50, + tagPositiveSolidFontHover: primitiveLightColors.green500, + tagPositiveLightBackground: primitiveLightColors.green50, + tagPositiveLightHover: primitiveLightColors.green100, + tagPositiveLightActive: primitiveLightColors.green200, + tagPositiveLightDisabled: primitiveLightColors.green50, + tagPositiveLightFont: primitiveLightColors.green600, + tagPositiveLightFontHover: primitiveLightColors.green700, + tagPositiveOutlinedActive: primitiveLightColors.green800, + tagPositiveOutlinedFontHover: primitiveLightColors.green700, tagPositiveOutlinedHover: tagHoverBackground, // Warning - tagWarningFontDisabled: themePrimitives.warning300, + tagWarningFontDisabled: primitiveLightColors.yellow200, - tagWarningOutlinedDisabled: themePrimitives.warning300, - tagWarningSolidFont: themePrimitives.warning700, - tagWarningSolidBackground: themePrimitives.warning400, - tagWarningOutlinedBackground: themePrimitives.warning300, - tagWarningOutlinedFont: themePrimitives.warning600, + tagWarningOutlinedDisabled: primitiveLightColors.yellow200, + tagWarningSolidFont: primitiveLightColors.yellow900, + tagWarningSolidBackground: primitiveLightColors.yellow300, + tagWarningOutlinedBackground: primitiveLightColors.yellow50, + tagWarningOutlinedFont: primitiveLightColors.yellow700, // Deprecated - tagWarningSolidHover: themePrimitives.warning50, - - tagWarningSolidActive: themePrimitives.warning100, - tagWarningSolidDisabled: themePrimitives.warning50, - tagWarningSolidFontHover: themePrimitives.warning500, - tagWarningLightBackground: themePrimitives.warning50, - tagWarningLightHover: themePrimitives.warning100, - tagWarningLightActive: themePrimitives.warning100, - tagWarningLightDisabled: themePrimitives.warning50, - tagWarningLightFont: themePrimitives.warning500, - tagWarningLightFontHover: themePrimitives.warning500, - tagWarningOutlinedActive: themePrimitives.warning600, - tagWarningOutlinedFontHover: themePrimitives.warning600, + tagWarningSolidHover: primitiveLightColors.yellow50, + + tagWarningSolidActive: primitiveLightColors.yellow100, + tagWarningSolidDisabled: primitiveLightColors.yellow50, + tagWarningSolidFontHover: primitiveLightColors.yellow500, + tagWarningLightBackground: primitiveLightColors.yellow50, + tagWarningLightHover: primitiveLightColors.yellow100, + tagWarningLightActive: primitiveLightColors.yellow200, + tagWarningLightDisabled: primitiveLightColors.yellow50, + tagWarningLightFont: primitiveLightColors.yellow600, + tagWarningLightFontHover: primitiveLightColors.yellow700, + tagWarningOutlinedActive: primitiveLightColors.yellow800, + tagWarningOutlinedFontHover: primitiveLightColors.yellow700, tagWarningOutlinedHover: tagHoverBackground, // Negative - tagNegativeFontDisabled: themePrimitives.negative200, + tagNegativeFontDisabled: primitiveLightColors.red300, - tagNegativeOutlinedDisabled: themePrimitives.negative200, - tagNegativeSolidFont: themePrimitives.white, - tagNegativeSolidBackground: themePrimitives.negative400, - tagNegativeOutlinedBackground: themePrimitives.negative200, - tagNegativeOutlinedFont: themePrimitives.negative400, + tagNegativeOutlinedDisabled: primitiveLightColors.red200, + tagNegativeSolidFont: primitiveLightColors.white, + tagNegativeSolidBackground: primitiveLightColors.red600, + tagNegativeOutlinedBackground: primitiveLightColors.red50, + tagNegativeOutlinedFont: primitiveLightColors.red700, // Deprecated - tagNegativeSolidHover: themePrimitives.negative50, - - tagNegativeSolidActive: themePrimitives.negative100, - tagNegativeSolidDisabled: themePrimitives.negative50, - tagNegativeSolidFontHover: themePrimitives.negative500, - tagNegativeLightBackground: themePrimitives.negative50, - tagNegativeLightHover: themePrimitives.negative100, - tagNegativeLightActive: themePrimitives.negative100, - tagNegativeLightDisabled: themePrimitives.negative50, - tagNegativeLightFont: themePrimitives.negative500, - tagNegativeLightFontHover: themePrimitives.negative500, - tagNegativeOutlinedActive: themePrimitives.negative600, - tagNegativeOutlinedFontHover: themePrimitives.negative400, + tagNegativeSolidHover: primitiveLightColors.red50, + + tagNegativeSolidActive: primitiveLightColors.red100, + tagNegativeSolidDisabled: primitiveLightColors.red50, + tagNegativeSolidFontHover: primitiveLightColors.red500, + tagNegativeLightBackground: primitiveLightColors.red50, + tagNegativeLightHover: primitiveLightColors.red100, + tagNegativeLightActive: primitiveLightColors.red200, + tagNegativeLightDisabled: primitiveLightColors.red50, + tagNegativeLightFont: primitiveLightColors.red600, + tagNegativeLightFontHover: primitiveLightColors.red700, + tagNegativeOutlinedActive: primitiveLightColors.red800, + tagNegativeOutlinedFontHover: primitiveLightColors.red700, tagNegativeOutlinedHover: tagHoverBackground, // Table - tableHeadBackgroundColor: themePrimitives.mono100, + tableHeadBackgroundColor: semanticColors.backgroundPrimary, - tableBackground: themePrimitives.mono100, - tableStripedBackground: themePrimitives.mono200, - tableFilter: themePrimitives.mono600, - tableFilterHeading: themePrimitives.mono700, - tableFilterBackground: themePrimitives.mono100, - tableFilterFooterBackground: themePrimitives.mono200, + tableBackground: semanticColors.backgroundPrimary, + tableStripedBackground: semanticColors.backgroundSecondary, + tableFilter: semanticColors.contentTertiary, + tableFilterHeading: semanticColors.contentPrimary, + tableFilterBackground: semanticColors.backgroundPrimary, + tableFilterFooterBackground: semanticColors.backgroundSecondary, // Toast - toastText: themePrimitives.white, - - toastPrimaryText: themePrimitives.white, - toastInfoBackground: themePrimitives.accent400, - toastInfoText: themePrimitives.white, - toastPositiveBackground: themePrimitives.positive400, - toastPositiveText: themePrimitives.white, - toastWarningBackground: themePrimitives.warning400, - toastWarningText: themePrimitives.black, - toastNegativeBackground: themePrimitives.negative400, - toastNegativeText: themePrimitives.white, + toastText: semanticColors.contentOnColor, + + toastPrimaryText: semanticColors.contentOnColor, + toastInfoBackground: semanticColors.backgroundAccent, + toastInfoText: semanticColors.contentOnColor, + toastPositiveBackground: semanticColors.backgroundPositive, + toastPositiveText: semanticColors.contentOnColor, + toastWarningBackground: semanticColors.backgroundWarning, + toastWarningText: semanticColors.contentOnColorInverse, + toastNegativeBackground: semanticColors.backgroundNegative, + toastNegativeText: semanticColors.contentOnColor, // Spinner - spinnerTrackFill: themePrimitives.mono900, + spinnerTrackFill: semanticColors.backgroundTertiary, // Progress bar - progressbarTrackFill: themePrimitives.mono900, + progressbarTrackFill: semanticColors.backgroundTertiary, // Tooltip - tooltipBackground: themePrimitives.mono900, + tooltipBackground: semanticColors.backgroundInverseSecondary, + + tooltipText: semanticColors.contentInversePrimary, - tooltipText: themePrimitives.mono100, + // Rating + ratingInactiveFill: semanticColors.backgroundPrimary, + ratingStroke: semanticColors.contentPrimary, }); diff --git a/src/themes/light-theme/color-tokens.ts b/src/themes/light-theme/color-foundation-tokens.ts similarity index 79% rename from src/themes/light-theme/color-tokens.ts rename to src/themes/light-theme/color-foundation-tokens.ts index 1bc13e8e98..8561b8f1a7 100644 --- a/src/themes/light-theme/color-tokens.ts +++ b/src/themes/light-theme/color-foundation-tokens.ts @@ -4,15 +4,15 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import { colors } from '../../tokens'; -import type { FoundationColorTokens } from '../types'; +import { primitiveLightColors } from '../../tokens'; +import type { FoundationColors } from '../types'; // color constants -const lightColorTokens: FoundationColorTokens = { +const foundationColors: FoundationColors = { // Primary Palette - primaryA: colors.black, - primaryB: colors.white, - primary: colors.black, + primaryA: primitiveLightColors.black, + primaryB: primitiveLightColors.white, + primary: '#000000', primary50: '#F6F6F6', primary100: '#EEEEEE', primary200: '#E2E2E2', @@ -22,7 +22,7 @@ const lightColorTokens: FoundationColorTokens = { primary600: '#545454', primary700: '#333333', // Accent Palette - accent: colors.blue400, + accent: primitiveLightColors.blue600, accent50: '#EFF3FE', accent100: '#D4E2FC', accent200: '#A0BFF8', @@ -32,7 +32,7 @@ const lightColorTokens: FoundationColorTokens = { accent600: '#174291', accent700: '#102C60', // Negative Palette - negative: colors.red400, + negative: primitiveLightColors.red600, negative50: '#FFEFED', negative100: '#FED7D2', negative200: '#F1998E', @@ -42,7 +42,7 @@ const lightColorTokens: FoundationColorTokens = { negative600: '#870F00', negative700: '#5A0A00', // Warning Palette - warning: colors.yellow400, + warning: primitiveLightColors.yellow300, warning50: '#FFFAF0', warning100: '#FFF2D9', warning200: '#FFE3AC', @@ -52,7 +52,7 @@ const lightColorTokens: FoundationColorTokens = { warning600: '#996F00', warning700: '#674D1B', // Positive Palette - positive: colors.green500, + positive: primitiveLightColors.green600, positive50: '#E6F2ED', positive100: '#ADDEC9', positive200: '#66D19E', @@ -74,9 +74,6 @@ const lightColorTokens: FoundationColorTokens = { mono800: '#545454', mono900: '#333333', mono1000: '#000000', - // Rating Palette, - ratingInactiveFill: colors.gray100, - ratingStroke: colors.gray300, }; -export default lightColorTokens; +export default foundationColors; diff --git a/src/themes/light-theme/color-semantic-tokens.ts b/src/themes/light-theme/color-semantic-tokens.ts index fb7255d0c8..a915d299f8 100644 --- a/src/themes/light-theme/color-semantic-tokens.ts +++ b/src/themes/light-theme/color-semantic-tokens.ts @@ -5,97 +5,100 @@ This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ import type { - FoundationColorTokens, - CoreSemanticColorTokens, - CoreExtensionSemanticColorTokens, - DeprecatedSemanticColorTokens, - SemanticColorTokens, + FoundationColors, + CoreSemanticColors, + CoreExtensionSemanticColors, + DeprecatedSemanticColors, + SemanticColors, } from '../types'; -import defaultFoundationColorTokens from './color-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; import { hexToRgb as hexToRgba } from '../../styles/util'; -import colors from '../../tokens/colors'; +import primitiveLightColors from '../../tokens/color-primitive-tokens'; export default ( // themePrimitives or foundation colors - foundation: FoundationColorTokens = defaultFoundationColorTokens -): SemanticColorTokens => { - const core: CoreSemanticColorTokens = { + foundation: FoundationColors = defaultFoundationColors +): SemanticColors => { + const core: CoreSemanticColors = { // Background backgroundPrimary: foundation.primaryB, - backgroundSecondary: colors.gray50, - backgroundTertiary: colors.gray100, + backgroundSecondary: primitiveLightColors.gray50, + backgroundTertiary: primitiveLightColors.gray100, backgroundInversePrimary: foundation.primaryA, - backgroundInverseSecondary: colors.gray800, + backgroundInverseSecondary: primitiveLightColors.gray900, // Content contentPrimary: foundation.primaryA, - contentSecondary: colors.gray600, - contentTertiary: colors.gray500, + contentSecondary: primitiveLightColors.gray800, + contentTertiary: primitiveLightColors.gray700, contentInversePrimary: foundation.primaryB, - contentInverseSecondary: colors.gray300, - contentInverseTertiary: colors.gray400, + contentInverseSecondary: primitiveLightColors.gray200, + contentInverseTertiary: primitiveLightColors.gray400, // Border - borderOpaque: colors.gray200, - // @ts-ignore - borderTransparent: hexToRgba(foundation.primaryA, '0.08'), + borderOpaque: primitiveLightColors.gray50, + borderTransparent: + hexToRgba(foundation.primaryA, '0.08') || + hexToRgba(defaultFoundationColors.primaryA, '0.08') || + '', borderSelected: foundation.primaryA, - borderInverseOpaque: colors.gray700, - // @ts-ignore - borderInverseTransparent: hexToRgba(foundation.primaryB, '0.2'), + borderInverseOpaque: primitiveLightColors.gray800, + borderInverseTransparent: + hexToRgba(foundation.primaryB, '0.2') || + hexToRgba(defaultFoundationColors.primaryB, '0.2') || + '', borderInverseSelected: foundation.primaryB, }; - const coreExtensions: CoreExtensionSemanticColorTokens = { + const coreExtensions: CoreExtensionSemanticColors = { // Backgrounds - backgroundStateDisabled: colors.gray50, - // @ts-ignore - backgroundOverlay: hexToRgba(colors.black, '0.5'), - // @ts-ignore - backgroundOverlayArt: hexToRgba(colors.black, '0.00'), + backgroundStateDisabled: primitiveLightColors.gray50, + backgroundOverlay: hexToRgba(primitiveLightColors.black, '0.5') || '', + backgroundOverlayArt: hexToRgba(primitiveLightColors.black, '0.00') || '', backgroundAccent: foundation.accent, backgroundNegative: foundation.negative, backgroundWarning: foundation.warning, - backgroundPositive: colors.green400, - backgroundAccentLight: colors.blue50, - backgroundNegativeLight: colors.red50, - backgroundWarningLight: colors.yellow50, - backgroundPositiveLight: colors.green50, - backgroundAlwaysDark: colors.black, - backgroundAlwaysLight: colors.white, + backgroundPositive: foundation.positive, + backgroundAccentLight: primitiveLightColors.blue50, + backgroundNegativeLight: primitiveLightColors.red50, + backgroundWarningLight: primitiveLightColors.yellow50, + backgroundPositiveLight: primitiveLightColors.green50, + backgroundAlwaysDark: primitiveLightColors.black, + backgroundAlwaysLight: primitiveLightColors.white, // Content - contentStateDisabled: colors.gray400, - contentAccent: foundation.accent, - contentOnColor: colors.white, - contentOnColorInverse: colors.black, - contentNegative: foundation.negative, - contentWarning: colors.yellow600, - contentPositive: colors.green400, + contentStateDisabled: primitiveLightColors.gray400, + contentOnColor: primitiveLightColors.white, + contentOnColorInverse: primitiveLightColors.black, + contentAccent: primitiveLightColors.blue600, + contentNegative: primitiveLightColors.red600, + contentWarning: primitiveLightColors.yellow600, + contentPositive: primitiveLightColors.green600, // Border - borderStateDisabled: colors.gray50, - borderAccent: colors.blue400, - borderAccentLight: colors.blue200, - borderNegative: colors.red200, - borderWarning: colors.yellow200, - borderPositive: colors.green200, - borderNegativeLight: colors.red200, - borderWarningLight: colors.yellow200, - borderPositiveLight: colors.green200, + borderStateDisabled: primitiveLightColors.gray50, + borderAccent: primitiveLightColors.blue600, + borderAccentLight: primitiveLightColors.blue300, + borderNegative: primitiveLightColors.red600, + borderNegativeLight: primitiveLightColors.red300, + borderWarning: primitiveLightColors.yellow600, + borderWarningLight: primitiveLightColors.yellow200, + borderPositive: primitiveLightColors.green600, + borderPositiveLight: primitiveLightColors.green300, // Programs - safety: colors.blue400, - eatsGreen400: colors.green400, - freightBlue400: colors.cobalt400, - jumpRed400: colors.red400, - rewardsTier1: colors.blue400, - rewardsTier2: colors.yellow400, - rewardsTier3: colors.platinum400, - rewardsTier4: colors.black, - membership: colors.yellow600, + safety: primitiveLightColors.blue600, + eatsGreen400: primitiveLightColors.green600, + freightBlue400: primitiveLightColors.cobalt400, + rewardsTier1: primitiveLightColors.blue600, + rewardsTier2: primitiveLightColors.yellow300, + rewardsTier3: primitiveLightColors.platinum400, + rewardsTier4: primitiveLightColors.black, + membership: primitiveLightColors.yellow600, }; - const deprecated: DeprecatedSemanticColorTokens = { + /** @deprecated these tokens are deprecated */ + const deprecated: DeprecatedSemanticColors = { + jumpRed400: primitiveLightColors.red400, backgroundOverlayLight: coreExtensions.backgroundOverlay, backgroundOverlayDark: coreExtensions.backgroundOverlay, backgroundLightAccent: coreExtensions.backgroundAccentLight, diff --git a/src/themes/light-theme/create-light-theme.ts b/src/themes/light-theme/create-light-theme.ts index 6c23b2c14f..27485b0816 100644 --- a/src/themes/light-theme/create-light-theme.ts +++ b/src/themes/light-theme/create-light-theme.ts @@ -4,53 +4,33 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import animation from '../shared/animation'; -import borders from '../shared/borders'; -import breakpoints from '../shared/breakpoints'; import deepMerge from '../../utils/deep-merge'; -import { getFoundationColorTokenOverrides } from '../utils'; -import defaultFoundationColorTokens from './color-tokens'; -import { colors as primitiveColorTokens } from '../../tokens'; -import getComponentColorTokens from './color-component-tokens'; -import getSemanticColorTokens from './color-semantic-tokens'; -import typography from '../shared/typography'; -import grid from '../shared/grid'; -import lighting from '../shared/lighting'; -import mediaQuery from '../shared/media-query'; -import sizing from '../shared/sizing'; +import { getFoundationColorOverrides } from '../utils'; +import { primitiveColors } from '../../tokens'; +import getComponentColors from './color-component-tokens'; +import getSemanticColors from './color-semantic-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; +import { LightTheme } from './light-theme'; -import type { Theme, MakeExtendable, DeepPartial } from '../../styles/types'; +import type { DeepPartial, MakeExtendable, Theme } from '../../styles/types'; export default function createLightTheme< OverridesT extends DeepPartial<MakeExtendable<Theme>> = {} >(overrides?: OverridesT): Theme & OverridesT { - const foundationColorTokens = { - ...defaultFoundationColorTokens, - ...getFoundationColorTokenOverrides(overrides?.colors), + const foundationColors = { + ...defaultFoundationColors, + ...getFoundationColorOverrides(overrides?.colors), }; - const semanticColorTokens = getSemanticColorTokens(foundationColorTokens); - const componentColorTokens = getComponentColorTokens(foundationColorTokens); + const semanticColors = getSemanticColors(foundationColors); + const componentColors = getComponentColors(semanticColors); const theme = { - animation, - borders, - breakpoints, + ...structuredClone(LightTheme), colors: { - ...primitiveColorTokens, - ...foundationColorTokens, - ...semanticColorTokens, - ...componentColorTokens, - }, - direction: 'auto', - grid, - lighting, - mediaQuery, - sizing, - typography, - // TODO(#2318) Remove in v11, the next major version. - // Do not use. - zIndex: { - modal: 2000, + ...primitiveColors, + ...foundationColors, + ...semanticColors, + ...componentColors, }, }; diff --git a/src/themes/light-theme/light-theme.ts b/src/themes/light-theme/light-theme.ts index 12389cab7c..c78382ba23 100644 --- a/src/themes/light-theme/light-theme.ts +++ b/src/themes/light-theme/light-theme.ts @@ -4,10 +4,10 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import foundationColorTokens from './color-tokens'; -import primitiveColorTokens from '../../tokens/colors'; -import getSemanticColorTokens from './color-semantic-tokens'; -import getComponentColorTokens from './color-component-tokens'; +import foundationColors from './color-foundation-tokens'; +import primitiveColors from '../../tokens/color-primitive-tokens'; +import getSemanticColors from './color-semantic-tokens'; +import getComponentColors from './color-component-tokens'; import borders from '../shared/borders'; import lighting from '../shared/lighting'; import typography from '../shared/typography'; @@ -22,10 +22,10 @@ import type { Theme } from '../../styles/types'; export const LightTheme: Theme = { name: 'light-theme', colors: { - ...foundationColorTokens, - ...primitiveColorTokens, - ...getComponentColorTokens(), - ...getSemanticColorTokens(), + ...foundationColors, + ...primitiveColors, + ...getComponentColors(), + ...getSemanticColors(), }, animation, breakpoints, diff --git a/src/themes/light-theme/primitives.ts b/src/themes/light-theme/primitives.ts index 8e54e5b39a..f5a5b19cd6 100644 --- a/src/themes/light-theme/primitives.ts +++ b/src/themes/light-theme/primitives.ts @@ -4,14 +4,14 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import defaultFoundationColorTokens from './color-tokens'; +import defaultFoundationColors from './color-foundation-tokens'; import { fontTokens } from '../shared/typography'; import type { Primitives } from '../types'; // We don't use this ourselves. We provide it for backward compatibility. // People may have used it to create their own custom theme. const primitives: Primitives = { - ...defaultFoundationColorTokens, + ...defaultFoundationColors, ...fontTokens, }; diff --git a/src/themes/types.ts b/src/themes/types.ts index af53cefd0e..9d5864cc24 100644 --- a/src/themes/types.ts +++ b/src/themes/types.ts @@ -23,78 +23,132 @@ export type LineStyle = | 'ridge' | 'solid'; -export type FoundationColorTokens = { +export type FoundationColors = { // Primary Palette primaryA: string; primaryB: string; + /** @deprecated Use semantic tokens instead. */ primary: string; + /** @deprecated Use semantic tokens instead. */ primary50: string; + /** @deprecated Use semantic tokens instead. */ primary100: string; + /** @deprecated Use semantic tokens instead. */ primary200: string; + /** @deprecated Use semantic tokens instead. */ primary300: string; + /** @deprecated Use semantic tokens instead. */ primary400: string; + /** @deprecated Use semantic tokens instead. */ primary500: string; + /** @deprecated Use semantic tokens instead. */ primary600: string; + /** @deprecated Use semantic tokens instead. */ primary700: string; + // Accent Palette accent: string; + /** @deprecated Use semantic tokens instead. */ accent50: string; + /** @deprecated Use semantic tokens instead. */ accent100: string; + /** @deprecated Use semantic tokens instead. */ accent200: string; + /** @deprecated Use semantic tokens instead. */ accent300: string; + /** @deprecated Use semantic tokens instead. */ accent400: string; + /** @deprecated Use semantic tokens instead. */ accent500: string; + /** @deprecated Use semantic tokens instead. */ accent600: string; + /** @deprecated Use semantic tokens instead. */ accent700: string; + // Alert Palette negative: string; negative50: string; + /** @deprecated Use semantic tokens instead. */ negative100: string; + /** @deprecated Use semantic tokens instead. */ negative200: string; + /** @deprecated Use semantic tokens instead. */ negative300: string; + /** @deprecated Use semantic tokens instead. */ negative400: string; + /** @deprecated Use semantic tokens instead. */ negative500: string; + /** @deprecated Use semantic tokens instead. */ negative600: string; + /** @deprecated Use semantic tokens instead. */ negative700: string; + // Warning Palette warning: string; + /** @deprecated Use semantic tokens instead. */ warning50: string; + /** @deprecated Use semantic tokens instead. */ warning100: string; + /** @deprecated Use semantic tokens instead. */ warning200: string; + /** @deprecated Use semantic tokens instead. */ warning300: string; + /** @deprecated Use semantic tokens instead. */ warning400: string; + /** @deprecated Use semantic tokens instead. */ warning500: string; + /** @deprecated Use semantic tokens instead. */ warning600: string; + /** @deprecated Use semantic tokens instead. */ warning700: string; + // Success Palette positive: string; + /** @deprecated Use semantic tokens instead. */ positive50: string; + /** @deprecated Use semantic tokens instead. */ positive100: string; + /** @deprecated Use semantic tokens instead. */ positive200: string; + /** @deprecated Use semantic tokens instead. */ positive300: string; + /** @deprecated Use semantic tokens instead. */ positive400: string; + /** @deprecated Use semantic tokens instead. */ positive500: string; + /** @deprecated Use semantic tokens instead. */ positive600: string; + /** @deprecated Use semantic tokens instead. */ positive700: string; + // Monochrome Palette + /** @deprecated Use semantic tokens instead. */ white: string; + /** @deprecated Use semantic tokens instead. */ black: string; + /** @deprecated Use semantic tokens instead. */ mono100: string; + /** @deprecated Use semantic tokens instead. */ mono200: string; + /** @deprecated Use semantic tokens instead. */ mono300: string; + /** @deprecated Use semantic tokens instead. */ mono400: string; + /** @deprecated Use semantic tokens instead. */ mono500: string; + /** @deprecated Use semantic tokens instead. */ mono600: string; + /** @deprecated Use semantic tokens instead. */ mono700: string; + /** @deprecated Use semantic tokens instead. */ mono800: string; + /** @deprecated Use semantic tokens instead. */ mono900: string; + /** @deprecated Use semantic tokens instead. */ mono1000: string; - // Rating Palette - ratingInactiveFill: string; - ratingStroke: string; }; -export type CoreSemanticColorTokens = { +export type CoreSemanticColors = { // Background backgroundPrimary: string; backgroundSecondary: string; @@ -110,17 +164,17 @@ export type CoreSemanticColorTokens = { contentInverseTertiary: string; // Border borderOpaque: string; - borderTransparent?: string; + borderTransparent: string; borderSelected: string; borderInverseOpaque: string; - borderInverseTransparent?: string; + borderInverseTransparent: string; borderInverseSelected: string; }; -export type CoreExtensionSemanticColorTokens = { +export type CoreExtensionSemanticColors = { // Backgrounds backgroundStateDisabled: string; backgroundOverlay: string; - backgroundOverlayArt?: string; + backgroundOverlayArt: string; backgroundAccent: string; backgroundNegative: string; backgroundWarning: string; @@ -153,7 +207,6 @@ export type CoreExtensionSemanticColorTokens = { safety: string; eatsGreen400: string; freightBlue400: string; - jumpRed400: string; rewardsTier1: string; rewardsTier2: string; rewardsTier3: string; @@ -161,20 +214,29 @@ export type CoreExtensionSemanticColorTokens = { membership: string; }; -export type DeprecatedSemanticColorTokens = { +export type DeprecatedSemanticColors = { + /** @deprecated this color token is deprecated */ + jumpRed400: string; + /** @deprecated this color token is deprecated */ backgroundOverlayLight: string; + /** @deprecated this color token is deprecated */ backgroundOverlayDark: string; + /** @deprecated this color token is deprecated */ backgroundLightAccent: string; + /** @deprecated this color token is deprecated */ backgroundLightPositive: string; + /** @deprecated this color token is deprecated */ backgroundLightWarning: string; + /** @deprecated this color token is deprecated */ backgroundLightNegative: string; }; -export type SemanticColorTokens = {} & CoreSemanticColorTokens & - CoreExtensionSemanticColorTokens & - DeprecatedSemanticColorTokens; +export type SemanticColors = {} & CoreSemanticColors & + CoreExtensionSemanticColors & + DeprecatedSemanticColors; -export type ComponentColorTokens = { +export type ComponentColors = { + //Banner bannerActionLowInfo: string; bannerActionLowNegative: string; bannerActionLowPositive: string; @@ -183,6 +245,9 @@ export type ComponentColorTokens = { bannerActionHighNegative: string; bannerActionHighPositive: string; bannerActionHighWarning: string; + // BottomNavigation + bottomNavigationText: string; + bottomNavigationSelectedText: string; // Buttons buttonPrimaryFill: string; buttonPrimaryText: string; @@ -488,6 +553,9 @@ export type ComponentColorTokens = { // Tooltip tooltipBackground: string; tooltipText: string; + // Rating + ratingInactiveFill: string; + ratingStroke: string; }; export type FontTokens = { @@ -497,7 +565,7 @@ export type FontTokens = { }; // TODO(#2318) Deprecate in the next major version -export type Primitives = {} & FoundationColorTokens & FontTokens; +export type Primitives = {} & FoundationColors & FontTokens; export type Font = { fontFamily: string; diff --git a/src/themes/utils.ts b/src/themes/utils.ts index 26c9a81ba8..88c8b285ac 100644 --- a/src/themes/utils.ts +++ b/src/themes/utils.ts @@ -6,7 +6,7 @@ LICENSE file in the root directory of this source tree. */ import type { ColorTokens } from '../styles/types'; -const foundationColorTokens = [ +const foundationColors = [ 'primaryA', 'primaryB', 'primary', @@ -16,10 +16,10 @@ const foundationColorTokens = [ 'positive', ]; -export function getFoundationColorTokenOverrides(colors?: Partial<ColorTokens>) { +export function getFoundationColorOverrides(colors?: Partial<ColorTokens>) { if (!colors) return {}; - return foundationColorTokens.reduce((acc, key) => { + return foundationColors.reduce((acc, key) => { if (colors[key]) { acc[key] = colors[key]; } diff --git a/src/tile/tile-group.tsx b/src/tile/tile-group.tsx index 867fa3ac4c..42f6a7f238 100644 --- a/src/tile/tile-group.tsx +++ b/src/tile/tile-group.tsx @@ -6,7 +6,7 @@ LICENSE file in the root directory of this source tree. */ import * as React from 'react'; -import { getOverrides } from '../helpers/overrides'; +import { getOverrides, mergeOverrides } from '../helpers/overrides'; import type { TileGroupOverrides, TileGroupProps } from './types'; import { StyledTileGroupRoot } from './styled-components'; import { isIndexSelected } from './utils'; @@ -146,14 +146,17 @@ const TileGroup = ({ onClick(event, index); } }, - overrides: { - Root: { - props: { - 'aria-checked': isSelected, - role: isRadioGroup ? 'radio' : 'checkbox', + overrides: mergeOverrides( + { + Root: { + props: { + 'aria-checked': isSelected, + role: isRadioGroup ? 'radio' : 'checkbox', + }, }, }, - }, + child.props.overrides + ), }); })} </Root> diff --git a/src/timezonepicker/update-tzdata.js b/src/timezonepicker/update-tzdata.js index 2963f0630f..bbadbeb8a7 100644 --- a/src/timezonepicker/update-tzdata.js +++ b/src/timezonepicker/update-tzdata.js @@ -5,7 +5,6 @@ This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ /* eslint-env node */ -// @flow const fs = require('fs'); const path = require('path'); diff --git a/src/tokens/color-primitive-tokens.ts b/src/tokens/color-primitive-tokens.ts new file mode 100644 index 0000000000..31587a2395 --- /dev/null +++ b/src/tokens/color-primitive-tokens.ts @@ -0,0 +1,324 @@ +/* +Copyright (c) Uber Technologies, Inc. + +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. +*/ +import type { PrimitiveColors, PrimitiveLightColors, PrimitiveDarkColors } from './types'; + +const primitiveColors: PrimitiveColors = { + /***** light color tokens *****/ + white: '#FFFFFF', + gray50: '#F3F3F3', + gray100: '#E8E8E8', + gray200: '#DDDDDD', + gray300: '#C6C6C6', + gray400: '#A6A6A6', + gray500: '#868686', + gray600: '#727272', + gray700: '#5E5E5E', + gray800: '#4B4B4B', + gray900: '#282828', + black: '#000000', + + /** @deprecated use gray color tokens instead */ + platinum50: '#F4FAFB', + /** @deprecated use gray color tokens instead */ + platinum100: '#EBF5F7', + /** @deprecated use gray color tokens instead */ + platinum200: '#CCDFE5', + /** @deprecated use gray color tokens instead */ + platinum300: '#A1BDCA', + /** @deprecated use gray color tokens instead */ + platinum400: '#8EA3AD', + /** @deprecated use gray color tokens instead */ + platinum500: '#6C7C83', + /** @deprecated use gray color tokens instead */ + platinum600: '#556268', + /** @deprecated use gray color tokens instead */ + platinum700: '#394145', + /** @deprecated use gray color tokens instead */ + platinum800: '#142328', + + red50: '#FFF0EE', + red100: '#FFE1DE', + red200: '#FFD2CD', + red300: '#FFB2AB', + red400: '#FC7F79', + red500: '#F83446', + red600: '#DE1135', + red700: '#BB032A', + red800: '#950F22', + red900: '#520810', + + orange50: '#FFF0E9', + orange100: '#FEE2D4', + orange200: '#FFD3BC', + orange300: '#FFB48C', + orange400: '#FC823A', + orange500: '#E65300', + orange600: '#C54600', + orange700: '#A33B04', + orange800: '#823006', + orange900: '#461A00', + + amber50: '#FFF1E1', + amber100: '#FFE4B7', + amber200: '#FFD5A1', + amber300: '#FFB749', + amber400: '#DF9500', + amber500: '#C46E00', + amber600: '#A95F03', + amber700: '#904A07', + amber800: '#763A00', + amber900: '#401E04', + + yellow50: '#FDF2DC', + yellow100: '#FBE5B6', + yellow200: '#FFD688', + yellow300: '#F6BC2F', + yellow400: '#D79900', + yellow500: '#B97502', + yellow600: '#9F6402', + yellow700: '#845201', + yellow800: '#6B4100', + yellow900: '#392300', + + lime50: '#EEF6E3', + lime100: '#DEEEC6', + lime200: '#CAE6A0', + lime300: '#A6D467', + lime400: '#77B71C', + lime500: '#5B9500', + lime600: '#4F7F06', + lime700: '#3F6900', + lime800: '#365310', + lime900: '#1B2D00', + + green50: '#EAF6ED', + green100: '#D3EFDA', + green200: '#B1EAC2', + green300: '#7FD99A', + green400: '#06C167', + green500: '#009A51', + green600: '#0E8345', + green700: '#166C3B', + green800: '#0D572D', + green900: '#002F14', + + teal50: '#E2F8FB', + teal100: '#CDEEF3', + teal200: '#B0E7EF', + teal300: '#77D5E3', + teal400: '#01B8CA', + teal500: '#0095A4', + teal600: '#007F8C', + teal700: '#016974', + teal800: '#1A535A', + teal900: '#002D33', + + blue50: '#EFF4FE', + blue100: '#DEE9FE', + blue200: '#CDDEFF', + blue300: '#A9C9FF', + blue400: '#6DAAFB', + blue500: '#068BEE', + blue600: '#276EF1', + blue700: '#175BCC', + blue800: '#1948A3', + blue900: '#002661', + + /* @deprecated use blue color tokens instead */ + cobalt50: '#EBEDFA', + /* @deprecated use blue color tokens instead */ + cobalt100: '#D2D7F0', + /* @deprecated use blue color tokens instead */ + cobalt200: '#949CE3', + /* @deprecated use blue color tokens instead */ + cobalt300: '#535FCF', + /* @deprecated use blue color tokens instead */ + cobalt400: '#0E1FC1', + /* @deprecated use blue color tokens instead */ + cobalt500: '#0A1899', + /* @deprecated use blue color tokens instead */ + cobalt600: '#081270', + /* @deprecated use blue color tokens instead */ + cobalt700: '#050C4D', + + purple50: '#F9F1FF', + purple100: '#F2E3FF', + purple200: '#EBD5FF', + purple300: '#DDB9FF', + purple400: '#C490F9', + purple500: '#A964F7', + purple600: '#944DE7', + purple700: '#7C3EC3', + purple800: '#633495', + purple900: '#3A1659', + + magenta50: '#FEEFF9', + magenta100: '#FEDFF3', + magenta200: '#FFCEF2', + magenta300: '#FFACE5', + magenta400: '#F877D2', + magenta500: '#E142BC', + magenta600: '#CA26A5', + magenta700: '#A91A90', + magenta800: '#891869', + magenta900: '#50003F', + + /* @deprecated use orange color tokens instead */ + brown50: '#F6F0EA', + /* @deprecated use orange color tokens instead */ + brown100: '#EBE0DB', + /* @deprecated use orange color tokens instead */ + brown200: '#D2BBB0', + /* @deprecated use orange color tokens instead */ + brown300: '#B18977', + /* @deprecated use orange color tokens instead */ + brown400: '#99644C', + /* @deprecated use orange color tokens instead */ + brown500: '#744C3A', + /* @deprecated use orange color tokens instead */ + brown600: '#5C3C2E', + /* @deprecated use orange color tokens instead */ + brown700: '#3D281E', + + /***** dark color tokens *****/ + gray50Dark: '#161616', + gray100Dark: '#292929', + gray200Dark: '#383838', + gray300Dark: '#484848', + gray400Dark: '#5D5D5D', + gray500Dark: '#717171', + gray600Dark: '#8C8C8C', + gray700Dark: '#ABABAB', + gray800Dark: '#C4C4C4', + gray900Dark: '#DEDEDE', + + red50Dark: '#2E0608', + red100Dark: '#4A1216', + red200Dark: '#621C20', + red300Dark: '#7F1F26', + red400Dark: '#A32C34', + red500Dark: '#C33840', + red600Dark: '#DE5B5D', + red700Dark: '#EA9B98', + red800Dark: '#EFBCB9', + red900Dark: '#F2D7D5', + + orange50Dark: '#260F03', + orange100Dark: '#401F0C', + orange200Dark: '#562A12', + orange300Dark: '#6D3715', + orange400Dark: '#8C4922', + orange500Dark: '#AB5727', + orange600Dark: '#C97245', + orange700Dark: '#ED9E74', + orange800Dark: '#F1BDA3', + orange900Dark: '#F8D6C5', + + amber50Dark: '#241003', + amber100Dark: '#3C220F', + amber200Dark: '#502F18', + amber300Dark: '#653D18', + amber400Dark: '#805127', + amber500Dark: '#956724', + amber600Dark: '#B68131', + amber700Dark: '#DEA85E', + amber800Dark: '#EEC28D', + amber900Dark: '#F6D9B7', + + yellow50Dark: '#211201', + yellow100Dark: '#39240A', + yellow200Dark: '#4C3111', + yellow300Dark: '#624013', + yellow400Dark: '#7A5616', + yellow500Dark: '#916C1A', + yellow600Dark: '#AE8523', + yellow700Dark: '#D7AC57', + yellow800Dark: '#E6C681', + yellow900Dark: '#F3DCAE', + + lime50Dark: '#0F1A03', + lime100Dark: '#202E13', + lime200Dark: '#2C3F19', + lime300Dark: '#39501F', + lime400Dark: '#4A682B', + lime500Dark: '#5A7E35', + lime600Dark: '#759954', + lime700Dark: '#9EC080', + lime800Dark: '#BDD4AB', + lime900Dark: '#D6E3CB', + + green50Dark: '#081B0E', + green100Dark: '#162F1E', + green200Dark: '#20402A', + green300Dark: '#2A5237', + green400Dark: '#306C44', + green500Dark: '#3D8351', + green600Dark: '#5C9D70', + green700Dark: '#8FC19C', + green800Dark: '#AED6B8', + green900Dark: '#CBE6D2', + + teal50Dark: '#071A1C', + teal100Dark: '#0C2E34', + teal200Dark: '#113F46', + teal300Dark: '#155158', + teal400Dark: '#216972', + teal500Dark: '#217F8B', + teal600Dark: '#3B9BA8', + teal700Dark: '#72C1CD', + teal800Dark: '#9CD5DF', + teal900Dark: '#C5E5EA', + + blue50Dark: '#061431', + blue100Dark: '#182946', + blue200Dark: '#22375C', + blue300Dark: '#2D4775', + blue400Dark: '#335BA3', + blue500Dark: '#3F6EC5', + blue600Dark: '#5E8BDB', + blue700Dark: '#93B4EE', + blue800Dark: '#B3CCF6', + blue900Dark: '#D1DFF6', + + purple50Dark: '#1B0E2D', + purple100Dark: '#2F2044', + purple200Dark: '#3F2D59', + purple300Dark: '#513974', + purple400Dark: '#694B96', + purple500Dark: '#7F5BB6', + purple600Dark: '#9A78CE', + purple700Dark: '#BDA7E4', + purple800Dark: '#D2C1EF', + purple900Dark: '#E2D9F5', + + magenta50Dark: '#28071F', + magenta100Dark: '#411636', + magenta200Dark: '#581F48', + magenta300Dark: '#6E2A5B', + magenta400Dark: '#8E3777', + magenta500Dark: '#AB4490', + magenta600Dark: '#C664A9', + magenta700Dark: '#E099C9', + magenta800Dark: '#EEB6DB', + magenta900Dark: '#F1D4E7', +}; + +const primitiveLightColors = {} as PrimitiveLightColors; +const primitiveDarkColors = {} as PrimitiveDarkColors; + +for (const key in primitiveColors) { + if (key.endsWith('Dark')) { + primitiveDarkColors[key] = primitiveColors[key]; + } else if (key === 'white' || key === 'black') { + primitiveLightColors[key] = primitiveColors[key]; + primitiveDarkColors[key] = primitiveColors[key]; + } else { + primitiveLightColors[key] = primitiveColors[key]; + } +} + +export { primitiveColors as default, primitiveLightColors, primitiveDarkColors }; diff --git a/src/tokens/colors.ts b/src/tokens/colors.ts deleted file mode 100644 index 79c9713d96..0000000000 --- a/src/tokens/colors.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright (c) Uber Technologies, Inc. - -This source code is licensed under the MIT license found in the -LICENSE file in the root directory of this source tree. -*/ -import type { PrimitiveColorTokens } from './types'; - -const colors: PrimitiveColorTokens = { - white: '#FFFFFF', - gray50: '#F6F6F6', - gray100: '#EEEEEE', - gray200: '#E2E2E2', - gray300: '#CBCBCB', - gray400: '#AFAFAF', - gray500: '#6B6B6B', - gray600: '#545454', - gray700: '#333333', - gray800: '#1F1F1F', - gray900: '#141414', - black: '#000000', - - platinum50: '#F4FAFB', - platinum100: '#EBF5F7', - platinum200: '#CCDFE5', - platinum300: '#A1BDCA', - platinum400: '#8EA3AD', - platinum500: '#6C7C83', - platinum600: '#556268', - platinum700: '#394145', - platinum800: '#142328', - - red50: '#FFEFED', - red100: '#FED7D2', - red200: '#F1998E', - red300: '#E85C4A', - red400: '#E11900', - red500: '#AB1300', - red600: '#870F00', - red700: '#5A0A00', - - orange50: '#FFF3EF', - orange100: '#FFE1D6', - orange200: '#FABDA5', - orange300: '#FA9269', - orange400: '#FF6937', - orange500: '#C14F29', - orange600: '#9A3F21', - orange700: '#672A16', - - yellow50: '#FFFAF0', - yellow100: '#FFF2D9', - yellow200: '#FFE3AC', - yellow300: '#FFCF70', - yellow400: '#FFC043', - yellow500: '#BC8B2C', - yellow600: '#996F00', - yellow700: '#674D1B', - - lime50: '#F2F7ED', - lime100: '#E0ECD2', - lime200: '#BCD69D', - lime300: '#8BB956', - lime400: '#67A421', - lime500: '#4E7C19', - lime600: '#3D6213', - lime700: '#29410d', - - green50: '#E6F2ED', - green100: '#ADDEC9', - green200: '#66D19E', - green300: '#06C167', - green400: '#048848', - green500: '#03703C', - green600: '#03582F', - green700: '#10462D', - - teal50: '#EDF5F7', - teal100: '#D2E8EC', - teal200: '#9DCDD6', - teal300: '#56A9B9', - teal400: '#218EA4', - teal500: '#196B7C', - teal600: '#135562', - teal700: '#0D3841', - - blue50: '#EFF3FE', - blue100: '#D4E2FC', - blue200: '#A0BFF8', - blue300: '#5B91F5', - blue400: '#276EF1', - blue500: '#1E54B7', - blue600: '#174291', - blue700: '#102C60', - - cobalt50: '#EBEDFA', - cobalt100: '#D2D7F0', - cobalt200: '#949CE3', - cobalt300: '#535FCF', - cobalt400: '#0E1FC1', - cobalt500: '#0A1899', - cobalt600: '#081270', - cobalt700: '#050C4D', - - purple50: '#F3F1F9', - purple100: '#E3DDF2', - purple200: '#C1B4E2', - purple300: '#957FCE', - purple400: '#7356BF', - purple500: '#574191', - purple600: '#453473', - purple700: '#2E224C', - - magenta50: '#F9F1F7', - magenta100: '#F2DDEB', - magenta200: '#E2B4D3', - magenta300: '#CE7EB3', - magenta400: '#BF569C', - magenta500: '#914176', - magenta600: '#72335D', - magenta700: '#4C223E', - - brown50: '#F6F0EA', - brown100: '#EBE0DB', - brown200: '#D2BBB0', - brown300: '#B18977', - brown400: '#99644C', - brown500: '#744C3A', - brown600: '#5C3C2E', - brown700: '#3D281E', -}; - -export default colors; diff --git a/src/tokens/index.ts b/src/tokens/index.ts index 3f933ba2cd..671b2128ee 100644 --- a/src/tokens/index.ts +++ b/src/tokens/index.ts @@ -4,10 +4,17 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -import type { PrimitiveColorTokens } from './types'; +import type { PrimitiveColors } from './types'; -export { default as colors } from './colors'; +export { + default as primitiveColors, + primitiveLightColors, + primitiveDarkColors, +} from './color-primitive-tokens'; + +// For backward compatibility: +export { default as colors } from './color-primitive-tokens'; export * from './types'; -/** @deprecated use PrimitiveColorTokens instead. To be removed in future versions.*/ -export type TokenColors = PrimitiveColorTokens; +/** @deprecated use PrimitiveColors instead. To be removed in future versions.*/ +export type TokenColors = PrimitiveColors; diff --git a/src/tokens/types.ts b/src/tokens/types.ts index f80288bdb6..9523e08afc 100644 --- a/src/tokens/types.ts +++ b/src/tokens/types.ts @@ -4,7 +4,7 @@ Copyright (c) Uber Technologies, Inc. This source code is licensed under the MIT license found in the LICENSE file in the root directory of this source tree. */ -export type PrimitiveColorTokens = { +export type PrimitiveColors = { white: string; black: string; gray50: string; @@ -17,14 +17,23 @@ export type PrimitiveColorTokens = { gray700: string; gray800: string; gray900: string; + /** @deprecated use gray color tokens instead */ platinum50: string; + /** @deprecated use gray color tokens instead */ platinum100: string; + /** @deprecated use gray color tokens instead */ platinum200: string; + /** @deprecated use gray color tokens instead */ platinum300: string; + /** @deprecated use gray color tokens instead */ platinum400: string; + /** @deprecated use gray color tokens instead */ platinum500: string; + /** @deprecated use gray color tokens instead */ platinum600: string; + /** @deprecated use gray color tokens instead */ platinum700: string; + /** @deprecated use gray color tokens instead */ platinum800: string; blue50: string; blue100: string; @@ -34,6 +43,8 @@ export type PrimitiveColorTokens = { blue500: string; blue600: string; blue700: string; + blue800: string; + blue900: string; teal50: string; teal100: string; teal200: string; @@ -42,6 +53,8 @@ export type PrimitiveColorTokens = { teal500: string; teal600: string; teal700: string; + teal800: string; + teal900: string; red50: string; red100: string; red200: string; @@ -50,6 +63,8 @@ export type PrimitiveColorTokens = { red500: string; red600: string; red700: string; + red800: string; + red900: string; green50: string; green100: string; green200: string; @@ -58,6 +73,8 @@ export type PrimitiveColorTokens = { green500: string; green600: string; green700: string; + green800: string; + green900: string; orange50: string; orange100: string; orange200: string; @@ -66,6 +83,18 @@ export type PrimitiveColorTokens = { orange500: string; orange600: string; orange700: string; + orange800: string; + orange900: string; + amber50: string; + amber100: string; + amber200: string; + amber300: string; + amber400: string; + amber500: string; + amber600: string; + amber700: string; + amber800: string; + amber900: string; magenta50: string; magenta100: string; magenta200: string; @@ -74,6 +103,8 @@ export type PrimitiveColorTokens = { magenta500: string; magenta600: string; magenta700: string; + magenta800: string; + magenta900: string; purple50: string; purple100: string; purple200: string; @@ -82,6 +113,8 @@ export type PrimitiveColorTokens = { purple500: string; purple600: string; purple700: string; + purple800: string; + purple900: string; yellow50: string; yellow100: string; yellow200: string; @@ -90,6 +123,8 @@ export type PrimitiveColorTokens = { yellow500: string; yellow600: string; yellow700: string; + yellow800: string; + yellow900: string; lime50: string; lime100: string; lime200: string; @@ -98,20 +133,170 @@ export type PrimitiveColorTokens = { lime500: string; lime600: string; lime700: string; + lime800: string; + lime900: string; + /** @deprecated use orange color tokens instead */ brown50: string; + /** @deprecated use orange color tokens instead */ brown100: string; + /** @deprecated use orange color tokens instead */ brown200: string; + /** @deprecated use orange color tokens instead */ brown300: string; + /** @deprecated use orange color tokens instead */ brown400: string; + /** @deprecated use orange color tokens instead */ brown500: string; + /** @deprecated use orange color tokens instead */ brown600: string; + /** @deprecated use orange color tokens instead */ brown700: string; + /** @deprecated use blue color tokens instead */ cobalt50: string; + /** @deprecated use blue color tokens instead */ cobalt100: string; + /** @deprecated use blue color tokens instead */ cobalt200: string; + /** @deprecated use blue color tokens instead */ cobalt300: string; + /** @deprecated use blue color tokens instead */ cobalt400: string; + /** @deprecated use blue color tokens instead */ cobalt500: string; + /** @deprecated use blue color tokens instead */ cobalt600: string; + /** @deprecated use blue color tokens instead */ cobalt700: string; + + // dark color tokens + gray50Dark: string; + gray100Dark: string; + gray200Dark: string; + gray300Dark: string; + gray400Dark: string; + gray500Dark: string; + gray600Dark: string; + gray700Dark: string; + gray800Dark: string; + gray900Dark: string; + + red50Dark: string; + red100Dark: string; + red200Dark: string; + red300Dark: string; + red400Dark: string; + red500Dark: string; + red600Dark: string; + red700Dark: string; + red800Dark: string; + red900Dark: string; + + orange50Dark: string; + orange100Dark: string; + orange200Dark: string; + orange300Dark: string; + orange400Dark: string; + orange500Dark: string; + orange600Dark: string; + orange700Dark: string; + orange800Dark: string; + orange900Dark: string; + + amber50Dark: string; + amber100Dark: string; + amber200Dark: string; + amber300Dark: string; + amber400Dark: string; + amber500Dark: string; + amber600Dark: string; + amber700Dark: string; + amber800Dark: string; + amber900Dark: string; + + yellow50Dark: string; + yellow100Dark: string; + yellow200Dark: string; + yellow300Dark: string; + yellow400Dark: string; + yellow500Dark: string; + yellow600Dark: string; + yellow700Dark: string; + yellow800Dark: string; + yellow900Dark: string; + + lime50Dark: string; + lime100Dark: string; + lime200Dark: string; + lime300Dark: string; + lime400Dark: string; + lime500Dark: string; + lime600Dark: string; + lime700Dark: string; + lime800Dark: string; + lime900Dark: string; + + green50Dark: string; + green100Dark: string; + green200Dark: string; + green300Dark: string; + green400Dark: string; + green500Dark: string; + green600Dark: string; + green700Dark: string; + green800Dark: string; + green900Dark: string; + + teal50Dark: string; + teal100Dark: string; + teal200Dark: string; + teal300Dark: string; + teal400Dark: string; + teal500Dark: string; + teal600Dark: string; + teal700Dark: string; + teal800Dark: string; + teal900Dark: string; + + blue50Dark: string; + blue100Dark: string; + blue200Dark: string; + blue300Dark: string; + blue400Dark: string; + blue500Dark: string; + blue600Dark: string; + blue700Dark: string; + blue800Dark: string; + blue900Dark: string; + + purple50Dark: string; + purple100Dark: string; + purple200Dark: string; + purple300Dark: string; + purple400Dark: string; + purple500Dark: string; + purple600Dark: string; + purple700Dark: string; + purple800Dark: string; + purple900Dark: string; + + magenta50Dark: string; + magenta100Dark: string; + magenta200Dark: string; + magenta300Dark: string; + magenta400Dark: string; + magenta500Dark: string; + magenta600Dark: string; + magenta700Dark: string; + magenta800Dark: string; + magenta900Dark: string; +}; + +export type PrimitiveLightColors = { + [K in keyof PrimitiveColors as K extends `${infer _}Dark` ? never : K]: PrimitiveColors[K]; +}; + +export type PrimitiveDarkColors = { + [K in keyof PrimitiveColors as K extends `${infer _}Dark` | 'white' | 'black' + ? K + : never]: PrimitiveColors[K]; };