From 451c970d42cec1c8a8e8b9cbd11fa888c1456bbc Mon Sep 17 00:00:00 2001 From: Simon Jentsch Date: Tue, 3 Dec 2024 00:47:37 +0100 Subject: [PATCH 1/2] refactor: Use component primitives in other components --- src/Radio/Radio.tsx | 53 +++++++++++++------------ src/styleHelpers/componentPrimitive.tsx | 22 +++++----- src/styleHelpers/css.ts | 9 +++++ src/styleHelpers/interpolateStyles.ts | 29 ++++++++++++++ src/styleHelpers/styleProp.ts | 35 +++++----------- 5 files changed, 88 insertions(+), 60 deletions(-) create mode 100644 src/styleHelpers/css.ts create mode 100644 src/styleHelpers/interpolateStyles.ts diff --git a/src/Radio/Radio.tsx b/src/Radio/Radio.tsx index b4bc7c7..989fc3a 100644 --- a/src/Radio/Radio.tsx +++ b/src/Radio/Radio.tsx @@ -1,7 +1,5 @@ import React, { forwardRef } from 'react'; -import { css } from '@emotion/react'; -import styled from '@emotion/styled'; -import { getComponentStyle, transitionTransformer } from '../styleHelpers/getComponentStyle'; +import { transitionTransformer } from '../styleHelpers/getComponentStyle'; import { BaseCheckable, @@ -11,44 +9,47 @@ import { } from '../shared/BaseCheckable'; import { getCustomStyles } from '../utils/useCustomStyles'; import { baseStyle } from '../shared/baseStyle'; +import { componentPrimitive, getPrimitiveStyle } from '../styleHelpers'; +import { ifProp } from '../styleHelpers/styleProp'; +import { pabloCss } from '../styleHelpers/css'; -const radioBoxSize = (props) => css` - calc(${getComponentStyle('radio.handleSize.{size}')(props)} + 2 * (${getComponentStyle( - 'radio.innerPadding.{size}' - )(props)} + ${getComponentStyle('radio.borderWidth')(props)}px)) +const radioBoxSize = pabloCss>` + calc( + ${getPrimitiveStyle(['handleSize', (props) => props.size])} + 2 * ( + ${getPrimitiveStyle(['innerPadding', (props) => props.size])} + + ${getPrimitiveStyle('borderWidth')}px)) `; -const RadioBox = styled.div` +const RadioBox = componentPrimitive>(['radio'])` ${baseStyle} position: relative; width: ${radioBoxSize}; height: ${radioBoxSize}; border-radius: 50%; - padding: ${getComponentStyle('radio.innerPadding.{size}')}; - background-color: ${getComponentStyle('radio.backgroundColor')}; - border: ${getComponentStyle('radio.borderWidth')}px solid - ${getComponentStyle('radio.borderColor')}; + padding: ${getPrimitiveStyle(['innerPadding', (props) => props.size])}; + background-color: ${getPrimitiveStyle('backgroundColor')}; + border: ${getPrimitiveStyle('borderWidth')}px solid + ${getPrimitiveStyle('borderColor')}; opacity: ${(props) => (props.disabled ? 0.3 : 1)}; - transition: ${getComponentStyle('radio.boxTransition', transitionTransformer)}; + transition: ${getPrimitiveStyle('boxTransition', transitionTransformer)}; - ${(props) => - props.focus && - css` - box-shadow: 0 0 0 ${getComponentStyle('radio.focus.outlineSize')(props)} - ${getComponentStyle('radio.focus.outlineColor')(props)}; - `}; - } + ${ifProp( + 'focus', + pabloCss` + box-shadow: 0 0 0 ${getPrimitiveStyle(['focus', 'outlineSize'])} ${getPrimitiveStyle(['focus', 'outlineColor'])}; + ` + )} ${getCustomStyles('radio.styles', 'box')} `; -const RadioHandle = styled.div` +const RadioHandle = componentPrimitive>(['radio'])` ${baseStyle} - width: ${getComponentStyle('radio.handleSize.{size}')}; - height: ${getComponentStyle('radio.handleSize.{size}')}; - transform: scale(${(props: any) => (props.checked ? 1 : 0)}); + width: ${getPrimitiveStyle(['handleSize', (props) => props.size])}; + height: ${getPrimitiveStyle(['handleSize', (props) => props.size])}; + transform: scale(${ifProp('checked', 1, 0)}); border-radius: 50%; - transition: ${getComponentStyle('radio.handleTransition', transitionTransformer)}; - background-color: ${getComponentStyle('radio.handleColor')}; + transition: ${getPrimitiveStyle('handleTransition', transitionTransformer)}; + background-color: ${getPrimitiveStyle('handleColor')}; ${getCustomStyles('radio.styles', 'handle')} `; diff --git a/src/styleHelpers/componentPrimitive.tsx b/src/styleHelpers/componentPrimitive.tsx index d408d8e..515b11b 100644 --- a/src/styleHelpers/componentPrimitive.tsx +++ b/src/styleHelpers/componentPrimitive.tsx @@ -9,19 +9,18 @@ interface CreateComponentPrimitiveOptions tag?: T; } -type ComponentPrimitiveProps

= { +interface ComponentPrimitiveProps

{ componentPath: ComponentPath

; -} & P; +} const getPrimitiveStyle = -

>( - property: string | string[], +

( + property: string | ComponentPath

, transformFn?: (value: unknown) => string | number ) => - (props: I) => { - const componentPath = props.componentPath as ComponentPath

; - const propertyArray = guaranteeArray(property); - return getComponentStyle([...componentPath, ...propertyArray], transformFn)(props); + (props: P & ComponentPrimitiveProps

) => { + const propertyPath = guaranteeArray(property); + return getComponentStyle([...props.componentPath, ...propertyPath], transformFn)(props); }; const componentPrimitive = @@ -29,8 +28,11 @@ const componentPrimitive = componentPath: ComponentPath

, { tag = 'div' as any }: CreateComponentPrimitiveOptions = {} ) => - (template: TemplateStringsArray, ...styles: Array>>) => { - const StyledComponent = styled(tag)>( + ( + template: TemplateStringsArray, + ...styles: Array>> + ) => { + const StyledComponent = styled(tag)

>( template, ...styles, getPrimitiveStyle('css') diff --git a/src/styleHelpers/css.ts b/src/styleHelpers/css.ts new file mode 100644 index 0000000..506b986 --- /dev/null +++ b/src/styleHelpers/css.ts @@ -0,0 +1,9 @@ +import { css, type Interpolation } from '@emotion/react'; +import { interpolateStyles } from './interpolateStyles'; + +const pabloCss = +

(templates: TemplateStringsArray, ...styles: Interpolation

[]) => + (props) => + css(templates, ...interpolateStyles(styles, props)); + +export { pabloCss }; diff --git a/src/styleHelpers/interpolateStyles.ts b/src/styleHelpers/interpolateStyles.ts new file mode 100644 index 0000000..9346444 --- /dev/null +++ b/src/styleHelpers/interpolateStyles.ts @@ -0,0 +1,29 @@ +import type { Interpolation } from '@emotion/react'; + +type InterpolationFunction

= (props: P, prop?: keyof P) => Interpolation

; +type StyleInterpolation

= Interpolation

| InterpolationFunction

; + +const interpolateStyle =

( + style: StyleInterpolation

, + props: P, + prop?: keyof P +) => { + if (typeof style === 'function') { + return [style(props)]; + } + if (Array.isArray(style)) { + return interpolateStyles(style, props, prop); + } + return [style]; +}; + +const interpolateStyles =

( + styles: Interpolation

[], + props: P, + prop?: keyof P +) => { + return styles.flatMap((style) => interpolateStyle(style, props, prop)); +}; + +export { interpolateStyles, interpolateStyle }; +export type { StyleInterpolation }; diff --git a/src/styleHelpers/styleProp.ts b/src/styleHelpers/styleProp.ts index dccd3e1..a7264bd 100644 --- a/src/styleHelpers/styleProp.ts +++ b/src/styleHelpers/styleProp.ts @@ -1,28 +1,15 @@ -import type { Interpolation } from '@emotion/react'; -import type { CSSInterpolation } from '@emotion/serialize'; - -type InterpolationFunction

= (props: P, prop?: keyof P) => CSSInterpolation; -type StyleInterpolation

= Interpolation

| InterpolationFunction

; - -const interpolateCss =

( - style: StyleInterpolation

, - props: P, - prop?: keyof P -) => { - if (typeof style === 'function') { - return style(props, prop); - } - - if (Array.isArray(style)) { - return style.map((css) => interpolateCss(css, props)); - } - - return style; -}; +import { interpolateStyle, StyleInterpolation } from './interpolateStyles'; const ifProp = -

(prop: keyof P, style: StyleInterpolation

) => - (props: P) => - props[prop] ? interpolateCss(style, props, prop) : null; +

( + prop: keyof P, + style: StyleInterpolation

, + fallbackStyle?: StyleInterpolation

+ ) => + (props: P) => { + return props[prop] + ? interpolateStyle(style, props, prop) + : interpolateStyle(fallbackStyle, props, prop); + }; export { ifProp }; From 4983a6de92cb74688126079b9130fd4ce56a928b Mon Sep 17 00:00:00 2001 From: Simon Jentsch Date: Tue, 3 Dec 2024 16:48:36 +0100 Subject: [PATCH 2/2] Move more component style paths to use array instead of string --- .size-limit.js | 90 +++++++++++----------- benchmark/button/PabloButton.tsx | 2 +- src/Avatar/Avatar.tsx | 2 +- src/Button/Button.tsx | 6 +- src/ButtonBase/ButtonBase.tsx | 6 +- src/Card/Card.tsx | 8 +- src/Checkbox/Checkbox.tsx | 31 ++++---- src/IconButton/IconButton.tsx | 85 ++++++++++---------- src/Input/Input.tsx | 4 +- src/Link/Link.tsx | 32 ++++---- src/Modal/Modal.stories.tsx | 8 +- src/Modal/Modal.tsx | 14 ++-- src/NativeSelect/NativeSelect.tsx | 8 +- src/Radio/RadioGroup.tsx | 2 +- src/SidebarNav/SidebarNav.tsx | 4 +- src/SidebarNav/SidebarNavItem.tsx | 12 +-- src/Switch/Switch.tsx | 18 ++--- src/Tabs/Tab.tsx | 42 +++++----- src/Tabs/styles.ts | 10 +-- src/TextArea/TextArea.tsx | 4 +- src/ToastCard/ToastCard.tsx | 10 +-- src/ToastProvider/ToastStack.tsx | 2 +- src/Toolbar/Toolbar.tsx | 4 +- src/Toolbar/ToolbarDivider.tsx | 6 +- src/Tooltip/Tooltip.tsx | 10 +-- src/shared/BaseInput.tsx | 51 ++++++------ src/styleHelpers/componentPrimitive.tsx | 12 +-- src/styleHelpers/getComponentStyle.spec.ts | 10 +-- src/styleHelpers/getComponentStyle.ts | 18 +---- src/theme/types.ts | 3 + src/types.ts | 13 +++- 31 files changed, 268 insertions(+), 259 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index d56d092..f77c35c 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -1,22 +1,22 @@ export default [ - { - path: 'build/index.cjs', - name: 'Common JS', - limit: '40 kb', - running: false, - }, - { - path: 'build/Button/index.cjs', - name: 'Button direct import (Common JS)', - limit: '11.5 kb', - running: false, - }, - { - path: 'build/es/index.cjs', - name: 'ES Next', - limit: '40 kb', - running: false, - }, + // { + // path: 'build/index.cjs', + // name: 'Common JS', + // limit: '40 kb', + // running: false, + // }, + // { + // path: 'build/Button/index.cjs', + // name: 'Button direct import (Common JS)', + // limit: '11.5 kb', + // running: false, + // }, + // { + // path: 'build/es/index.cjs', + // name: 'ES Next', + // limit: '40 kb', + // running: false, + // }, { path: 'build/esm/index.js', name: 'Button Treeshaking', @@ -24,31 +24,31 @@ export default [ import: '{ Button, PabloThemeProvider }', limit: '10 kb', }, - { - path: 'build/esm/index.js', - name: 'Input Treeshaking', - import: '{ Input }', - limit: '7 kb', - running: false, - }, - { - path: 'build/esm/index.js', - name: 'Tooltip Treeshaking', - import: '{ Tooltip }', - limit: '10 kb', - running: false, - }, - { - path: 'build/esm/index.js', - name: 'ES Module', - limit : '34 kb', - running: false, - import: '*', - }, - { - path: 'build/pablo.min.js', - name: 'UMD Bundle', - limit: '32 kb', - running: false, - }, + // { + // path: 'build/esm/index.js', + // name: 'Input Treeshaking', + // import: '{ Input }', + // limit: '7 kb', + // running: false, + // }, + // { + // path: 'build/esm/index.js', + // name: 'Tooltip Treeshaking', + // import: '{ Tooltip }', + // limit: '10 kb', + // running: false, + // }, + // { + // path: 'build/esm/index.js', + // name: 'ES Module', + // limit : '34 kb', + // running: false, + // import: '*', + // }, + // { + // path: 'build/pablo.min.js', + // name: 'UMD Bundle', + // limit: '32 kb', + // running: false, + // }, ] diff --git a/benchmark/button/PabloButton.tsx b/benchmark/button/PabloButton.tsx index 4ddd474..e23b121 100644 --- a/benchmark/button/PabloButton.tsx +++ b/benchmark/button/PabloButton.tsx @@ -7,7 +7,7 @@ import { Style } from '../../src/theme/types'; import { getComponentStyle } from '../../src/utils/styleHelpers'; const getButtonOutlineShadow = (color: Style) => css` - box-shadow: 0 0 0 ${getComponentStyle('button.base.focus.outlineSize')} ${color}; + box-shadow: 0 0 0 ${getComponentStyle(['button', 'base', 'focus', 'outlineSize'])} ${color}; `; const PabloButton = styled.button` diff --git a/src/Avatar/Avatar.tsx b/src/Avatar/Avatar.tsx index 0b68e48..3e7a3d9 100644 --- a/src/Avatar/Avatar.tsx +++ b/src/Avatar/Avatar.tsx @@ -42,7 +42,7 @@ const AvatarImage = styled< ${baseStyle} ${conditionalStyles('variant', (props) => ({ square: css` - border-radius: ${getComponentStyle('avatar.square.borderRadius')(props)}px; + border-radius: ${getComponentStyle(['avatar', 'square', 'borderRadius'])(props)}px; `, circle: css` border-radius: 50%; diff --git a/src/Button/Button.tsx b/src/Button/Button.tsx index cb3e86f..bff71fa 100644 --- a/src/Button/Button.tsx +++ b/src/Button/Button.tsx @@ -52,9 +52,9 @@ const InnerButton = styled('button')([ width: props.fullWidth ? '100%' : 'inherit', ...getColorStyles(props), '&:focus': { - boxShadow: `0 0 0 ${getComponentStyle('button.base.focus.outlineSize')( + boxShadow: `0 0 0 ${getComponentStyle(['button', 'base', 'focus', 'outlineSize'])( props - )} ${getComponentStyle('button.{color}.outlineColor')(props)}`, + )} ${getComponentStyle(['button', props.color, 'outlineColor'])(props)}`, }, '&:hover:enabled': getColorStyles(props, 'hover'), '&:active:enabled': getColorStyles(props, 'active'), @@ -73,7 +73,7 @@ const IconBox = styled.div((props: IconBoxProps) => [ display: 'flex', justifyContent: 'center', alignItems: 'center', - [`margin-${props.marginSide}`]: getComponentStyle('button.base.icon.gap')(props), + [`margin-${props.marginSide}`]: getComponentStyle(['button', 'base', 'icon', 'gap'])(props), '&, & > *': { width: getComponentStyle('button.base.icon.size.{size}')(props), diff --git a/src/ButtonBase/ButtonBase.tsx b/src/ButtonBase/ButtonBase.tsx index 2fb647b..dd414ac 100644 --- a/src/ButtonBase/ButtonBase.tsx +++ b/src/ButtonBase/ButtonBase.tsx @@ -17,9 +17,9 @@ export const buttonBaseStyles: any = [ alignItems: 'center', textDecoration: 'none', padding: getComponentStyle('button.sizes.{size}.padding')(props), - border: `${getComponentStyle('button.base.borderSize')(props)}px solid transparent`, + border: `${getComponentStyle(['button', 'base', 'borderSize'])(props)}px solid transparent`, background: 'transparent', - borderRadius: getComponentStyle('button.base.borderRadius')(props), + borderRadius: getComponentStyle(['button', 'base', 'borderRadius'])(props), transition: getComponentStyle('button.base.transitions', transitionTransformer)(props), outline: 'none', @@ -28,7 +28,7 @@ export const buttonBaseStyles: any = [ }, '&:disabled': { - opacity: getComponentStyle('button.base.disabled.opacity')(props), + opacity: getComponentStyle(['button', 'base', 'disabled', 'opacity'])(props), cursor: 'normal', }, }), diff --git a/src/Card/Card.tsx b/src/Card/Card.tsx index af387ff..11c66c0 100644 --- a/src/Card/Card.tsx +++ b/src/Card/Card.tsx @@ -10,11 +10,11 @@ export interface CardProps extends LayoutBoxProps, BaseProps` ${baseStyle} - padding: ${getComponentStyle('card.padding')}; - background-color: ${getComponentStyle('card.backgroundColor')}; - color: ${getComponentStyle('card.color')}; + padding: ${getComponentStyle(['card', 'padding'])}; + background-color: ${getComponentStyle(['card', 'backgroundColor'])}; + color: ${getComponentStyle(['card', 'color'])}; box-shadow: ${getComponentStyle('card.shadow', shadowTransformer)}; - border-radius: ${getComponentStyle('card.borderRadius')}; + border-radius: ${getComponentStyle(['card', 'borderRadius'])}; ${(props) => props.css} ${layoutInterpolationFn} ${getCustomStyles('card.styles', 'root')} diff --git a/src/Checkbox/Checkbox.tsx b/src/Checkbox/Checkbox.tsx index 6c7ea48..b1ba7ae 100644 --- a/src/Checkbox/Checkbox.tsx +++ b/src/Checkbox/Checkbox.tsx @@ -10,11 +10,12 @@ import { } from '../shared/BaseCheckable'; import { getCustomStyles } from '../utils/useCustomStyles'; import { baseStyle } from '../shared/baseStyle'; +import { ifProp } from '../styleHelpers/styleProp'; const checkboxSize = (props) => css` - calc(${getComponentStyle('checkbox.handleSize.{size}')(props)} + 2 * (${getComponentStyle( + calc(${getComponentStyle(['checkbox', 'handleSize', props.size])(props)} + 2 * (${getComponentStyle( 'checkbox.innerPadding.{size}' - )(props)} + ${getComponentStyle('checkbox.borderWidth')(props)}px)) + )(props)} + ${getComponentStyle(['checkbox', 'borderWidth'])(props)}px)) `; const CheckboxBox = styled.div` @@ -22,11 +23,11 @@ const CheckboxBox = styled.div` position: relative; width: ${checkboxSize}; height: ${checkboxSize}; - border-radius: ${getComponentStyle('checkbox.outerBorderRadius')}; - padding: ${getComponentStyle('checkbox.innerPadding.{size}')}; - background-color: ${getComponentStyle('checkbox.backgroundColor')}; - border: ${getComponentStyle('checkbox.borderWidth')}px solid - ${getComponentStyle('checkbox.borderColor')}; + border-radius: ${getComponentStyle(['checkbox', 'outerBorderRadius'])}; + padding: ${getComponentStyle(['checkbox', 'innerPadding', (props) => props.size])}; + background-color: ${getComponentStyle(['checkbox', 'backgroundColor'])}; + border: ${getComponentStyle(['checkbox', 'borderWidth'])}px solid + ${getComponentStyle(['checkbox', 'borderColor'])}; opacity: ${(props) => (props.disabled ? 0.3 : 1)}; transition: ${getComponentStyle('checkbox.boxTransition', transitionTransformer)}; @@ -34,8 +35,8 @@ const CheckboxBox = styled.div` ${(props) => props.focus && css` - box-shadow: 0 0 0 ${getComponentStyle('checkbox.focus.outlineSize')(props)} - ${getComponentStyle('checkbox.focus.outlineColor')(props)}; + box-shadow: 0 0 0 ${getComponentStyle(['checkbox', 'focus', 'outlineSize'])(props)} + ${getComponentStyle(['checkbox', 'focus', 'outlineColor'])(props)}; `}; } ${getCustomStyles('checkbox.styles', 'box')} @@ -43,12 +44,12 @@ const CheckboxBox = styled.div` const CheckboxHandle = styled.div` ${baseStyle} - width: ${getComponentStyle('checkbox.handleSize.{size}')}; - height: ${getComponentStyle('checkbox.handleSize.{size}')}; - transform: scale(${(props: any) => (props.checked ? 1 : 0)}); - border-radius: ${getComponentStyle('checkbox.innerBorderRadius')}; - transition: ${getComponentStyle('checkbox.handleTransition')}; - background-color: ${getComponentStyle('checkbox.handleColor')}; + width: ${getComponentStyle(['checkbox', 'handleSize', (props) => props.size])}; + height: ${getComponentStyle(['checkbox', 'handleSize', (props) => props.size])}; + transform: scale(${ifProp('checked', 1, 0)}); + border-radius: ${getComponentStyle(['checkbox', 'innerBorderRadius'])}; + transition: ${getComponentStyle(['checkbox', 'handleTransition'])}; + background-color: ${getComponentStyle(['checkbox', 'handleColor'])}; ${getCustomStyles('checkbox.styles', 'handle')} `; diff --git a/src/IconButton/IconButton.tsx b/src/IconButton/IconButton.tsx index f42be5a..3df8a81 100644 --- a/src/IconButton/IconButton.tsx +++ b/src/IconButton/IconButton.tsx @@ -2,12 +2,14 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { LayoutBoxProps, layoutInterpolationFn } from '../Box'; import { baseStyle } from '../shared/baseStyle'; -import { getComponentStyle, transitionTransformer } from '../styleHelpers'; +import { componentPrimitive, getComponentStyle, transitionTransformer } from '../styleHelpers'; import { BaseProps, CssFunctionReturn } from '../types'; import { getCustomStyles } from '../utils/useCustomStyles'; import { IconButtonStyleProperties } from './styles'; import { omit } from '../utils/omit'; import React, { forwardRef, HTMLProps } from 'react'; +import { ifProp } from '../styleHelpers/styleProp'; +import { pabloCss } from '../styleHelpers/css'; export type IconButtonSize = 'small' | 'medium' | 'large'; export type IconButtonColor = 'brand' | 'plain' | 'negative' | 'positive'; @@ -23,33 +25,57 @@ export interface IconButtonProps children?: React.ReactNode; css?: CssFunctionReturn; } -const StyledIconButton = styled.button` + +const activeStyles = (props: IconButtonProps) => pabloCss` + background-color: ${getComponentStyle(['iconButton', props.color, 'active', 'backgroundColor'])}; + color: ${getComponentStyle(['iconButton', props.color, 'active', 'color'])}; + ${getCustomStyles('iconButton.styles', 'active')} +`; + +const nonActiveStyles = (props: IconButtonProps) => pabloCss` + &:hover:enabled { + background-color: ${getComponentStyle(['iconButton', props.color, 'hover', 'backgroundColor'])}; + color: ${getComponentStyle(['iconButton', props.color, 'hover', 'color'])}; + ${getCustomStyles('iconButton.styles', 'hover')} + } + + &:focus:enabled { + background-color: ${getComponentStyle(['iconButton', props.color, 'focus', 'backgroundColor'])}; + color: ${getComponentStyle(['iconButton', props.color, 'focus', 'color'])}; + ${getCustomStyles('iconButton.styles', 'focus')} + } +`; + +const StyledIconButton = componentPrimitive(['iconButton'], { + tag: 'button', +})` ${baseStyle} - width: ${getComponentStyle('iconButton.size.{size}')}; - height: ${getComponentStyle('iconButton.size.{size}')}; - background-color: ${getComponentStyle('iconButton.{color}.backgroundColor')}; - color: ${getComponentStyle('iconButton.{color}.color')}; + width: ${getComponentStyle((props) => ['iconButton', 'size', props.size])}; + height: ${getComponentStyle((props) => ['iconButton', 'size', props.size])}; + background-color: ${getComponentStyle((props) => ['iconButton', props.color, 'backgroundColor'])}; + color: ${getComponentStyle(['iconButton', '{color}', 'color'])}; border: 0; padding: 0; outline: 0; box-sizing: border-box; - transition: ${getComponentStyle('iconButton.transition', transitionTransformer)}; - border-radius: ${getComponentStyle('iconButton.borderRadius')}; + transition: ${getComponentStyle(['iconButton', 'transition'], transitionTransformer)}; + border-radius: ${getComponentStyle(['iconButton', 'borderRadius'])}; display: flex; justify-content: center; align-items: center; flex-shrink: 0; & > * { - width: ${getComponentStyle('iconButton.icon.size.{size}')}; - height: ${getComponentStyle('iconButton.icon.size.{size}')}; - transition: ${getComponentStyle('iconButton.icon.transition', transitionTransformer)}; + width: ${getComponentStyle((props) => ['iconButton', 'icon', 'size', props.size])}; + height: ${getComponentStyle((props) => ['iconButton', 'icon', 'size', props.size])}; + transition: ${getComponentStyle(['iconButton', 'icon', 'transition'], transitionTransformer)}; transform: scale( - ${(props) => - props.active - ? props.theme.componentStyles.iconButton.icon.active.scale - : props.theme.componentStyles.iconButton.icon.scale} + ${ifProp( + 'active', + (props) => props.theme.componentStyles.iconButton.icon.active.scale, + (props) => props.theme.componentStyles.iconButton.icon.scale + )} ); } @@ -58,7 +84,7 @@ const StyledIconButton = styled.button` } &:disabled { - opacity: ${getComponentStyle('iconButton.disabled.opacity')}; + opacity: ${getComponentStyle(['iconButton', 'disabled', 'opacity'])}; cursor: normal; } @@ -66,32 +92,7 @@ const StyledIconButton = styled.button` ${(props) => props.css} - ${(props) => - props.active - ? css` - background-color: ${getComponentStyle('iconButton.{color}.active.backgroundColor')( - props - )}; - color: ${getComponentStyle('iconButton.{color}.active.color')(props)}; - ${getCustomStyles('iconButton.styles', 'active')(props)} - ` - : css` - &:hover:enabled { - background-color: ${getComponentStyle('iconButton.{color}.hover.backgroundColor')( - props - )}; - color: ${getComponentStyle('iconButton.{color}.hover.color')(props)}; - ${getCustomStyles('iconButton.styles', 'hover')(props)} - } - - &:focus:enabled { - background-color: ${getComponentStyle('iconButton.{color}.focus.backgroundColor')( - props - )}; - color: ${getComponentStyle('iconButton.{color}.focus.color')(props)}; - ${getCustomStyles('iconButton.styles', 'focus')(props)} - } - `} + ${ifProp('active', activeStyles, nonActiveStyles)} ${(props) => layoutInterpolationFn(omit(props, ['size']))} `; diff --git a/src/Input/Input.tsx b/src/Input/Input.tsx index fbe3340..479f84d 100644 --- a/src/Input/Input.tsx +++ b/src/Input/Input.tsx @@ -25,9 +25,9 @@ const InnerInput = styled.input` flex-grow: 1; flex-shrink: 1; border: 0; - padding: ${getComponentStyle('input.padding')}; + padding: ${getComponentStyle(['input', 'padding'])}; background-color: transparent; - font-family: ${getComponentStyle('input.fontFamily')}; + font-family: ${getComponentStyle(['input', 'fontFamily'])}; outline: none; ${getCustomStyles('input.styles', 'field')} width: 100%; diff --git a/src/Link/Link.tsx b/src/Link/Link.tsx index 67e0f4f..90da5f1 100644 --- a/src/Link/Link.tsx +++ b/src/Link/Link.tsx @@ -9,32 +9,32 @@ export type LinkProps = BaseProps; export const Link = styled.a` ${baseStyle} - color: ${getComponentStyle('link.color')}; - text-decoration: ${getComponentStyle('link.textDecoration')}; - font-style: ${getComponentStyle('link.fontStyle')}; - font-weight: ${getComponentStyle('link.fontWeight')}; + color: ${getComponentStyle(['link', 'color'])}; + text-decoration: ${getComponentStyle(['link', 'textDecoration'])}; + font-style: ${getComponentStyle(['link', 'fontStyle'])}; + font-weight: ${getComponentStyle(['link', 'fontWeight'])}; outline: 0; ${getCustomStyles('link.styles', 'root')} &:hover { - color: ${getComponentStyle('link.hover.color')}; - text-decoration: ${getComponentStyle('link.hover.textDecoration')}; - font-style: ${getComponentStyle('link.hover.fontStyle')}; - font-weight: ${getComponentStyle('link.hover.fontWeight')}; + color: ${getComponentStyle(['link', 'hover', 'color'])}; + text-decoration: ${getComponentStyle(['link', 'hover', 'textDecoration'])}; + font-style: ${getComponentStyle(['link', 'hover', 'fontStyle'])}; + font-weight: ${getComponentStyle(['link', 'hover', 'fontWeight'])}; ${getCustomStyles('link.styles', 'hover')} } &:visited { - color: ${getComponentStyle('link.visited.color')}; - text-decoration: ${getComponentStyle('link.visited.textDecoration')}; - font-style: ${getComponentStyle('link.visited.fontStyle')}; - font-weight: ${getComponentStyle('link.visited.fontWeight')}; + color: ${getComponentStyle(['link', 'visited', 'color'])}; + text-decoration: ${getComponentStyle(['link', 'visited', 'textDecoration'])}; + font-style: ${getComponentStyle(['link', 'visited', 'fontStyle'])}; + font-weight: ${getComponentStyle(['link', 'visited', 'fontWeight'])}; ${getCustomStyles('link.styles', 'visited')} } &:focus { - color: ${getComponentStyle('link.focus.color')}; - text-decoration: ${getComponentStyle('link.focus.textDecoration')}; - font-style: ${getComponentStyle('link.focus.fontStyle')}; - font-weight: ${getComponentStyle('link.focus.fontWeight')}; + color: ${getComponentStyle(['link', 'focus', 'color'])}; + text-decoration: ${getComponentStyle(['link', 'focus', 'textDecoration'])}; + font-style: ${getComponentStyle(['link', 'focus', 'fontStyle'])}; + font-weight: ${getComponentStyle(['link', 'focus', 'fontWeight'])}; ${getCustomStyles('link.styles', 'focus')} } `; diff --git a/src/Modal/Modal.stories.tsx b/src/Modal/Modal.stories.tsx index 963a148..547d99e 100644 --- a/src/Modal/Modal.stories.tsx +++ b/src/Modal/Modal.stories.tsx @@ -138,11 +138,11 @@ WithCustomStyles.args = { `, area: (props) => css` background-color: rgba(0, 0, 255, 0.2); - border-radius: ${getComponentStyle('modal.box.borderRadius')(props)}px; + border-radius: ${getComponentStyle(['modal', 'box', 'borderRadius'])(props)}px; `, paneBox: (props) => css` border: 5px solid blue; - border-radius: ${getComponentStyle('modal.box.borderRadius')(props)}px; + border-radius: ${getComponentStyle(['modal', 'box', 'borderRadius'])(props)}px; padding: ${getSpacing(1)(props)}; `, }, @@ -175,11 +175,11 @@ export const WithCustomStylesFromTheme = () => { `, area: (props) => css` background-color: rgba(0, 0, 255, 0.2); - border-radius: ${getComponentStyle('modal.box.borderRadius')(props)}px; + border-radius: ${getComponentStyle(['modal', 'box', 'borderRadius'])(props)}px; `, paneBox: (props) => css` border: 5px solid blue; - border-radius: ${getComponentStyle('modal.box.borderRadius')(props)}px; + border-radius: ${getComponentStyle(['modal', 'box', 'borderRadius'])(props)}px; padding: ${getSpacing(1)(props)}; `, }, diff --git a/src/Modal/Modal.tsx b/src/Modal/Modal.tsx index 89c5d7c..cb75c9c 100644 --- a/src/Modal/Modal.tsx +++ b/src/Modal/Modal.tsx @@ -42,7 +42,7 @@ const Backdrop = styled.div` left: 0; top: 0; overflow: scroll; - background-color: ${getComponentStyle('modal.backdropColor')}; + background-color: ${getComponentStyle(['modal', 'backdropColor'])}; display: flex; justify-content: center; align-items: center; @@ -71,7 +71,7 @@ const ModalArea = styled.div` ${(props) => !props.open && css` - transform: ${getComponentStyle('modal.box.closedTransform')(props)}; + transform: ${getComponentStyle(['modal', 'box', 'closedTransform'])(props)}; `} ${(props) => props.animate && @@ -81,7 +81,7 @@ const ModalArea = styled.div` max-width: ${getComponentStyle('modal.box.maxWidth.{maxWidth}')}; margin: auto; min-height: min-content; - padding: ${getComponentStyle('modal.padding')}; + padding: ${getComponentStyle(['modal', 'padding'])}; ${(props) => props.css} `; @@ -91,16 +91,16 @@ interface ModalBoxProps { const ModalBox = styled.div` ${baseStyle} - border-radius: ${getComponentStyle('modal.box.borderRadius')}px; - background-color: ${getComponentStyle('modal.box.backgroundColor')}; - padding: ${getComponentStyle('modal.box.padding')}; + border-radius: ${getComponentStyle(['modal', 'box', 'borderRadius'])}px; + background-color: ${getComponentStyle(['modal', 'box', 'backgroundColor'])}; + padding: ${getComponentStyle(['modal', 'box', 'padding'])}; box-shadow: ${getComponentStyle('modal.box.shadow', shadowTransformer)}; ${(props) => props.css} `; const PaneBox = styled.div` ${baseStyle} - margin-top: ${getComponentStyle('modal.gap')}; + margin-top: ${getComponentStyle(['modal', 'gap'])}; ${(props) => props.css} `; diff --git a/src/NativeSelect/NativeSelect.tsx b/src/NativeSelect/NativeSelect.tsx index b72ae6e..adf4115 100644 --- a/src/NativeSelect/NativeSelect.tsx +++ b/src/NativeSelect/NativeSelect.tsx @@ -26,11 +26,11 @@ const StyledSelect = styled.select` flex-grow: 1; border: 0; appearance: none; - padding: ${getComponentStyle('nativeSelect.padding')}; + padding: ${getComponentStyle(['nativeSelect', 'padding'])}; background-color: transparent; - font-family: ${getComponentStyle('nativeSelect.fontFamily')}; + font-family: ${getComponentStyle(['nativeSelect', 'fontFamily'])}; outline: none; - padding-right: ${getComponentStyle('nativeSelect.reservedArrowSpace')}; + padding-right: ${getComponentStyle(['nativeSelect', 'reservedArrowSpace'])}; position: relative; ${getCustomStyles('nativeSelect.styles', 'field')} `; @@ -46,7 +46,7 @@ const SelectWrapper = styled.div` width: 8px; height: 6px; position: absolute; - right: ${getComponentStyle('nativeSelect.arrowGap')}; + right: ${getComponentStyle(['nativeSelect', 'arrowGap'])}; top: 50%; transform: translateY(-50%); } diff --git a/src/Radio/RadioGroup.tsx b/src/Radio/RadioGroup.tsx index b35db88..10b3a74 100644 --- a/src/Radio/RadioGroup.tsx +++ b/src/Radio/RadioGroup.tsx @@ -9,7 +9,7 @@ import { baseStyle } from '../shared/baseStyle'; const RadioGroupItem = styled.div<{ size: CheckableSize }>` ${baseStyle} - margin-bottom: ${getComponentStyle('radio.groupItemGap.{size}')}; + margin-bottom: ${getComponentStyle((props) => ['radio', 'groupItemGap', props.size])}; `; export interface RadioGroupProps extends BoxProps { diff --git a/src/SidebarNav/SidebarNav.tsx b/src/SidebarNav/SidebarNav.tsx index 3932e80..0429003 100644 --- a/src/SidebarNav/SidebarNav.tsx +++ b/src/SidebarNav/SidebarNav.tsx @@ -14,8 +14,8 @@ export interface SidebarNavProps extends LayoutBoxProps, BaseProps` ${baseStyle} - border-left: ${getComponentStyle('sidebarNav.borderLeft')}; - padding-left: ${getComponentStyle('sidebarNav.borderLeftSpacing')}; + border-left: ${getComponentStyle(['sidebarNav', 'borderLeft'])}; + padding-left: ${getComponentStyle(['sidebarNav', 'borderLeftSpacing'])}; ${layoutInterpolationFn} ${getCustomStyles('sidebar.styles', 'root')} `; diff --git a/src/SidebarNav/SidebarNavItem.tsx b/src/SidebarNav/SidebarNavItem.tsx index 2d10335..e9e94cb 100644 --- a/src/SidebarNav/SidebarNavItem.tsx +++ b/src/SidebarNav/SidebarNavItem.tsx @@ -14,29 +14,29 @@ export interface SidebarNavItemProps extends ButtonBaseProps { const SidebarNavItemWrapper = styled.li` ${buttonBaseStyles} display: flex; - margin: ${getComponentStyle('sidebarNav.item.marginY')} 0; + margin: ${getComponentStyle(['sidebarNav', 'item', 'marginY'])} 0; ${getCustomStyles('sidebar.item.styles', 'root')} ${(props) => props.selected && css` - background-color: ${getComponentStyle('sidebarNav.item.selected.backgroundColor')(props)}; + background-color: ${getComponentStyle(['sidebarNav', 'item', 'selected', 'backgroundColor'])(props)}; ${getCustomStyles('sidebar.item.styles', 'selected')(props)} `} &:hover { - background-color: ${getComponentStyle('sidebarNav.item.hover.backgroundColor')}; + background-color: ${getComponentStyle(['sidebarNav', 'item', 'hover', 'backgroundColor'])}; ${getCustomStyles('sidebar.item.styles', 'hover')} } &:focus { - box-shadow: 0 0 0 ${getComponentStyle('sidebarNav.item.focus.outlineSize')} - ${getComponentStyle('sidebarNav.item.focus.outlineColor')}; + box-shadow: 0 0 0 ${getComponentStyle(['sidebarNav', 'item', 'focus', 'outlineSize'])} + ${getComponentStyle(['sidebarNav', 'item', 'focus', 'outlineColor'])}; ${getCustomStyles('sidebar.item.styles', 'focus')} } &:active { - background-color: ${getComponentStyle('sidebarNav.item.active.backgroundColor')}; + background-color: ${getComponentStyle(['sidebarNav', 'item', 'active', 'backgroundColor'])}; ${getCustomStyles('sidebar.item.styles', 'active')} } `; diff --git a/src/Switch/Switch.tsx b/src/Switch/Switch.tsx index 5c80ec3..f752cfb 100644 --- a/src/Switch/Switch.tsx +++ b/src/Switch/Switch.tsx @@ -14,7 +14,7 @@ import { baseStyle } from '../shared/baseStyle'; const switchBoxSize = (multiplier) => (props) => css` calc(${multiplier} * ${getComponentStyle('switch.handleSize.{size}')(props)} + 2 * (${getComponentStyle( 'switch.innerPadding.{size}' - )(props)} + ${getComponentStyle('switch.borderWidth')(props)}px)) + )(props)} + ${getComponentStyle(['switch', 'borderWidth'])(props)}px)) `; const SwitchBox = styled.div` @@ -26,13 +26,13 @@ const SwitchBox = styled.div` ( ${getComponentStyle('switch.handleSize.{size}')} + 2 * ${getComponentStyle('switch.innerPadding.{size}')} + 2 * - ${getComponentStyle('switch.borderWidth')}px + ${getComponentStyle(['switch', 'borderWidth'])}px ) * 0.5 ); padding: ${getComponentStyle('switch.innerPadding.{size}')}; - background-color: ${getComponentStyle('switch.backgroundColor')}; - border: ${getComponentStyle('switch.borderWidth')}px solid - ${getComponentStyle('switch.borderColor')}; + background-color: ${getComponentStyle(['switch', 'backgroundColor'])}; + border: ${getComponentStyle(['switch', 'borderWidth'])}px solid + ${getComponentStyle(['switch', 'borderColor'])}; opacity: ${(props) => (props.disabled ? 0.3 : 1)}; transition: ${getComponentStyle('switch.boxTransition', transitionTransformer)}; @@ -40,8 +40,8 @@ const SwitchBox = styled.div` ${(props) => props.focus && css` - box-shadow: 0 0 0 ${getComponentStyle('switch.focus.outlineSize')(props)} - ${getComponentStyle('switch.focus.outlineColor')(props)}; + box-shadow: 0 0 0 ${getComponentStyle(['switch', 'focus', 'outlineSize'])(props)} + ${getComponentStyle(['switch', 'focus', 'outlineColor'])(props)}; `}; } ${getCustomStyles('switch.styles', 'box')} @@ -58,8 +58,8 @@ const SwitchHandle = styled.div` transition: ${getComponentStyle('switch.handleTransition', transitionTransformer)}; background-color: ${(props) => props.checked - ? getComponentStyle('switch.handleColorChecked')(props) - : getComponentStyle('switch.handleColorUnchecked')(props)}; + ? getComponentStyle(['switch', 'handleColorChecked'])(props) + : getComponentStyle(['switch', 'handleColorUnchecked'])(props)}; ${getCustomStyles('switch.styles', 'handle')} `; diff --git a/src/Tabs/Tab.tsx b/src/Tabs/Tab.tsx index 151ec2d..c15fcc2 100644 --- a/src/Tabs/Tab.tsx +++ b/src/Tabs/Tab.tsx @@ -25,27 +25,27 @@ export type TabProps = InnerTabProps & const TabButton = styled.button>` ${buttonBaseStyles} - color: ${getComponentStyle('tabs.tab.color')}; + color: ${getComponentStyle(['tabs', 'tab', 'color'])}; position: relative; - padding: ${getComponentStyle('tabs.tab.padding')}; - margin: ${getComponentStyle('tabs.tab.margin')}; + padding: ${getComponentStyle(['tabs', 'tab', 'padding'])}; + margin: ${getComponentStyle(['tabs', 'tab', 'margin'])}; white-space: nowrap; ${getCustomStyles('tabs.tab.styles', 'root')} &:hover { - background-color: ${getComponentStyle('tabs.tab.hover.backgroundColor')}; + background-color: ${getComponentStyle(['tabs', 'tab', 'hover', 'backgroundColor'])}; ${getCustomStyles('tabs.tab.styles', 'hover')} } &:focus { - box-shadow: 0 0 0 ${getComponentStyle('button.base.focus.outlineSize')} - ${getComponentStyle('tabs.tab.focus.outlineColor')}; + box-shadow: 0 0 0 ${getComponentStyle(['button', 'base', 'focus', 'outlineSize'])} + ${getComponentStyle(['tabs', 'tab', 'focus', 'outlineColor'])}; ${getCustomStyles('tabs.tab.styles', 'focus')} } &:active { - background-color: ${getComponentStyle('tabs.tab.active.backgroundColor')}; + background-color: ${getComponentStyle(['tabs', 'tab', 'active', 'backgroundColor'])}; ${getCustomStyles('tabs.tab.styles', 'active')} } @@ -54,23 +54,23 @@ const TabButton = styled.button>` ${(props) => props.selected && css` - color: ${getComponentStyle('tabs.tab.selected.color')(props)}; - padding: ${getComponentStyle('tabs.tab.selected.padding')(props)}; - margin: ${getComponentStyle('tabs.tab.selected.margin')(props)}; + color: ${getComponentStyle(['tabs', 'tab', 'selected', 'color'])(props)}; + padding: ${getComponentStyle(['tabs', 'tab', 'selected', 'padding'])(props)}; + margin: ${getComponentStyle(['tabs', 'tab', 'selected', 'margin'])(props)}; &:after { content: ''; position: absolute; bottom: 0; - left: ${getComponentStyle('tabs.tab.selected.bottomBorder.gap')(props)}; - width: calc(100% - 2 * ${getComponentStyle('tabs.tab.selected.bottomBorder.gap')(props)}); - height: ${getComponentStyle('tabs.tab.selected.bottomBorder.thickness')(props)}; - border-top-left-radius: ${getComponentStyle('tabs.tab.selected.bottomBorder.radius')( + left: ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'gap'])(props)}; + width: calc(100% - 2 * ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'gap'])(props)}); + height: ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'thickness'])(props)}; + border-top-left-radius: ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'radius'])( props )}; - border-top-right-radius: ${getComponentStyle('tabs.tab.selected.bottomBorder.radius')( + border-top-right-radius: ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'radius'])( props )}; - background-color: ${getComponentStyle('tabs.tab.selected.bottomBorder.color')(props)}; + background-color: ${getComponentStyle(['tabs', 'tab', 'selected', 'bottomBorder', 'color'])(props)}; ${getCustomStyles('tabs.tab.styles', 'indicator')(props)} } ${getCustomStyles('tabs.tab.styles', 'selected')(props)} @@ -87,14 +87,14 @@ const IconBox = styled.div` ${(props) => props.hasText && css` - margin-right: ${getComponentStyle('tabs.tab.icon.gap')(props)}; + margin-right: ${getComponentStyle(['tabs', 'tab', 'icon', 'gap'])(props)}; `} - width: ${getComponentStyle('tabs.tab.icon.size')}; - height: ${getComponentStyle('tabs.tab.icon.size')}; + width: ${getComponentStyle(['tabs', 'tab', 'icon', 'size'])}; + height: ${getComponentStyle(['tabs', 'tab', 'icon', 'size'])}; & > * { - width: ${getComponentStyle('tabs.tab.icon.size')}; - height: ${getComponentStyle('tabs.tab.icon.size')}; + width: ${getComponentStyle(['tabs', 'tab', 'icon', 'size'])}; + height: ${getComponentStyle(['tabs', 'tab', 'icon', 'size'])}; } `; diff --git a/src/Tabs/styles.ts b/src/Tabs/styles.ts index 25d7fd3..0bdbc4a 100644 --- a/src/Tabs/styles.ts +++ b/src/Tabs/styles.ts @@ -58,21 +58,21 @@ export const tabsStyles: TabsStyles = { tab: { color: themeVars.colors.common.black, icon: { - gap: getComponentStyle('button.base.icon.gap'), - size: getComponentStyle('button.base.icon.size.medium'), + gap: getComponentStyle(['button', 'base', 'icon', 'gap']), + size: getComponentStyle(['button', 'base', 'icon', 'size', 'medium']), }, padding: (props) => css` ${getSpacing(1)(props)} ${getSpacing(1.5)(props)} ${getSpacing(1.5)(props)} `, margin: (props) => css`0 0 ${getSpacing(0)(props)}`, hover: { - backgroundColor: getComponentStyle('button.brand.text.hover.backgroundColor'), + backgroundColor: getComponentStyle(['button', 'brand', 'text', 'hover', 'backgroundColor']), }, focus: { - outlineColor: getComponentStyle('button.brand.outlineColor'), + outlineColor: getComponentStyle(['button', 'brand', 'outlineColor']), }, active: { - backgroundColor: getComponentStyle('button.brand.text.hover.backgroundColor'), + backgroundColor: getComponentStyle(['button', 'brand', 'text', 'hover', 'backgroundColor']), }, selected: { padding: (props) => css` diff --git a/src/TextArea/TextArea.tsx b/src/TextArea/TextArea.tsx index 4961fb9..230c53b 100644 --- a/src/TextArea/TextArea.tsx +++ b/src/TextArea/TextArea.tsx @@ -29,9 +29,9 @@ const InnerTextArea = styled.textarea` resize: none; border: 0; flex-grow: 1; - padding: ${getComponentStyle('textArea.padding')}; + padding: ${getComponentStyle(['textArea', 'padding'])}; background-color: transparent; - font-family: ${getComponentStyle('textArea.fontFamily')}; + font-family: ${getComponentStyle(['textArea', 'fontFamily'])}; outline: none; ${getCustomStyles('textArea.styles', 'field')} `; diff --git a/src/ToastCard/ToastCard.tsx b/src/ToastCard/ToastCard.tsx index 205d4eb..0415df0 100644 --- a/src/ToastCard/ToastCard.tsx +++ b/src/ToastCard/ToastCard.tsx @@ -17,11 +17,11 @@ import { useUniqueId } from '../utils/useUniqueId'; const CardWrapper = styled.div` ${baseStyle} - border-radius: ${getComponentStyle('toastCard.borderRadius')}; - max-width: ${getComponentStyle('toastCard.width')}; - background-color: ${getComponentStyle('toastCard.backgroundColor')}; - color: ${getComponentStyle('toastCard.color')}; - padding: ${getComponentStyle('toastCard.padding')}; + border-radius: ${getComponentStyle(['toastCard', 'borderRadius'])}; + max-width: ${getComponentStyle(['toastCard', 'width'])}; + background-color: ${getComponentStyle(['toastCard', 'backgroundColor'])}; + color: ${getComponentStyle(['toastCard', 'color'])}; + padding: ${getComponentStyle(['toastCard', 'padding'])}; box-shadow: ${getComponentStyle('toastCard.shadow', shadowTransformer)}; ${layoutInterpolationFn} ${getCustomStyles('toastCard.styles', 'card')} diff --git a/src/ToastProvider/ToastStack.tsx b/src/ToastProvider/ToastStack.tsx index 65aa5e3..fb1e7cc 100644 --- a/src/ToastProvider/ToastStack.tsx +++ b/src/ToastProvider/ToastStack.tsx @@ -13,7 +13,7 @@ export interface ToastStackProps { const ToastStackBox = styled.div` ${baseStyle} padding: ${getSpacing(1.5)}; - width: ${getComponentStyle('toastCard.width')}; + width: ${getComponentStyle(['toastCard', 'width'])}; position: fixed; display: flex; z-index: 1100; diff --git a/src/Toolbar/Toolbar.tsx b/src/Toolbar/Toolbar.tsx index cd288a5..2bce5f1 100644 --- a/src/Toolbar/Toolbar.tsx +++ b/src/Toolbar/Toolbar.tsx @@ -12,8 +12,8 @@ import { baseStyle } from '../shared/baseStyle'; const ToolbarBox = styled.div` ${baseStyle} display: flex; - margin: 0 -${getComponentStyle('toolbar.gap')}; - height: calc(${getComponentStyle('toolbar.item.size')} + 2 * ${getComponentStyle('toolbar.gap')}); + margin: 0 -${getComponentStyle(['toolbar', 'gap'])}; + height: calc(${getComponentStyle(['toolbar', 'item', 'size'])} + 2 * ${getComponentStyle(['toolbar', 'gap'])}); align-items: center; ${layoutInterpolationFn} ${getCustomStyles('toolbar.styles', 'root')} diff --git a/src/Toolbar/ToolbarDivider.tsx b/src/Toolbar/ToolbarDivider.tsx index a09a686..46bb5fe 100644 --- a/src/Toolbar/ToolbarDivider.tsx +++ b/src/Toolbar/ToolbarDivider.tsx @@ -7,10 +7,10 @@ import { ToolbarDividerStyleProperties } from './styles'; export const ToolbarDivider = styled('div')>` ${baseStyle} - margin: 0 ${getComponentStyle('toolbar.gap')}; - width: ${getComponentStyle('toolbar.divider.width')}px; + margin: 0 ${getComponentStyle(['toolbar', 'gap'])}; + width: ${getComponentStyle(['toolbar', 'divider', 'width'])}px; align-self: stretch; - background-color: ${getComponentStyle('toolbar.divider.color')}; + background-color: ${getComponentStyle(['toolbar', 'divider', 'color'])}; ${getCustomStyles('toolbar.divider.styles', 'root')} `; diff --git a/src/Tooltip/Tooltip.tsx b/src/Tooltip/Tooltip.tsx index 40f5b9e..8c86934 100644 --- a/src/Tooltip/Tooltip.tsx +++ b/src/Tooltip/Tooltip.tsx @@ -40,13 +40,13 @@ interface TooltipPopoverProps extends BaseProps { const TooltipPopover = styled.div` ${baseStyle} - z-index: ${getComponentStyle('tooltip.zIndex')}; + z-index: ${getComponentStyle(['tooltip', 'zIndex'])}; pointer-events: none; position: relative; - border-radius: ${getComponentStyle('tooltip.borderRadius')}px; - padding: ${getComponentStyle('tooltip.padding')}; - background-color: ${getComponentStyle('tooltip.backgroundColor')}; - color: ${getComponentStyle('tooltip.color')}; + border-radius: ${getComponentStyle(['tooltip', 'borderRadius'])}px; + padding: ${getComponentStyle(['tooltip', 'padding'])}; + background-color: ${getComponentStyle(['tooltip', 'backgroundColor'])}; + color: ${getComponentStyle(['tooltip', 'color'])}; white-space: nowrap; /* Bottom Arrow */ diff --git a/src/shared/BaseInput.tsx b/src/shared/BaseInput.tsx index 42c35ef..c35ff5b 100644 --- a/src/shared/BaseInput.tsx +++ b/src/shared/BaseInput.tsx @@ -3,7 +3,7 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { Box, layoutInterpolationFn } from '../Box'; import type { LayoutBoxProps } from '../Box'; -import type { Style } from '../theme/types'; +import type { InputComponentIdentifier, Style } from '../theme/types'; import { InfoText, ParagraphBold } from '../Typography'; import { hijackCbBefore } from '../utils/hijackCb'; import { getComponentStyle, transitionTransformer } from '../styleHelpers'; @@ -11,6 +11,8 @@ import { useUniqueId } from '../utils/useUniqueId'; import { BaseProps, CssFunctionReturn } from '../types'; import { useCustomStyles } from '../utils/useCustomStyles'; import { baseStyle } from './baseStyle'; +import { pabloCss } from '../styleHelpers/css'; +import { ifProp } from '../styleHelpers/styleProp'; export type BaseInputStyleProperties = | 'root' @@ -55,42 +57,45 @@ export type BaseInputOuterProps

, E extends HTMLEle 'onChange' > & BaseInputProps & { - name: string; + name: InputComponentIdentifier; inputComponent: React.FC

; adornmentGap?: Style | number; }; -interface InputWrapperProps extends LayoutBoxProps { +interface InputWrapperProps extends InnerInputProps { + name: 'textarea' | 'input'; fullWidth: boolean; focus: boolean; error: boolean; cssStyles: CssFunctionReturn; } +const focusStyles = pabloCss` + box-shadow: 0 0 0 + ${getComponentStyle((props) => [props.name, props.variant, 'focus', 'outlineSize'])} + ${getComponentStyle((props) => [props.name, props.variant, 'focus', 'outlineColor'])}; +`; + +const errorStyles = (props: InputWrapperProps) => + pabloCss` + border-color: ${getComponentStyle([props.name, props.variant, 'error', 'borderColor'])}; + &:focus { + box-shadow: 0 0 0 ${getComponentStyle([props.name, props.variant, 'focus', 'outlineSize'])} + ${getComponentStyle([props.name, props.variant, 'error', 'focus', 'outlineColor'])}; + } +`(props); + const InputWrapper = styled.div` ${baseStyle} display: flex; align-items: center; - border: ${getComponentStyle('{name}.borderWidth')}px solid - ${getComponentStyle('{name}.{variant}.borderColor')}; - border-radius: ${getComponentStyle('{name}.borderRadius')}; - background-color: ${getComponentStyle('{name}.{variant}.backgroundColor')}; - transition: ${getComponentStyle('{name}.transitions', transitionTransformer)}; - ${(props) => - props.focus && - css` - box-shadow: 0 0 0 ${getComponentStyle('{name}.{variant}.focus.outlineSize')(props)} - ${getComponentStyle('{name}.{variant}.focus.outlineColor')(props)}; - `} - ${(props) => - props.error && - css` - border-color: ${getComponentStyle('{name}.{variant}.error.borderColor')(props)}; - &:focus { - box-shadow: 0 0 0 ${getComponentStyle('{name}.{variant}.focus.outlineSize')(props)} - ${getComponentStyle('{name}.{variant}.error.focus.outlineColor')(props)}; - } - `} + border: ${getComponentStyle((props) => [props.name, 'borderWidth'])}px solid + ${getComponentStyle((props) => [props.name, props.variant, 'borderColor'])}; + border-radius: ${getComponentStyle((props) => [props.name, 'borderRadius'])}; + background-color: ${getComponentStyle((props) => [props.name, props.variant, 'backgroundColor'])}; + transition: ${getComponentStyle((props) => [props.name, 'transitions'], transitionTransformer)}; + ${ifProp('focus', focusStyles)} + ${ifProp('error', errorStyles)} ${layoutInterpolationFn} ${(props) => props.fullWidth && diff --git a/src/styleHelpers/componentPrimitive.tsx b/src/styleHelpers/componentPrimitive.tsx index 515b11b..a485e8e 100644 --- a/src/styleHelpers/componentPrimitive.tsx +++ b/src/styleHelpers/componentPrimitive.tsx @@ -9,23 +9,23 @@ interface CreateComponentPrimitiveOptions tag?: T; } -interface ComponentPrimitiveProps

{ - componentPath: ComponentPath

; +interface ComponentPrimitiveProps { + componentPath: ComponentPath; } const getPrimitiveStyle =

( - property: string | ComponentPath

, + property: string | ComponentPath, transformFn?: (value: unknown) => string | number ) => - (props: P & ComponentPrimitiveProps

) => { + (props: P & ComponentPrimitiveProps) => { const propertyPath = guaranteeArray(property); return getComponentStyle([...props.componentPath, ...propertyPath], transformFn)(props); }; const componentPrimitive = -

( - componentPath: ComponentPath

, +

( + componentPath: ComponentPath, { tag = 'div' as any }: CreateComponentPrimitiveOptions = {} ) => ( diff --git a/src/styleHelpers/getComponentStyle.spec.ts b/src/styleHelpers/getComponentStyle.spec.ts index a44db56..578e90f 100644 --- a/src/styleHelpers/getComponentStyle.spec.ts +++ b/src/styleHelpers/getComponentStyle.spec.ts @@ -26,7 +26,7 @@ beforeEach(() => { }); test('Get component style by simple path', () => { - const result = getComponentStyle('button.hover.color')({ theme }); + const result = getComponentStyle(['button', 'hover', 'color'])({ theme }); expect(result).toBe('black'); }); @@ -36,22 +36,22 @@ test('Get component style by interpolated path with props put into the path', () }); test('Get component style by interpolated path with unknown props and put prop name into path', () => { - const result = getComponentStyle('button.{unknownState}')({ theme, state: 'focus' }); + const result = getComponentStyle(['button', '{unknownState}'])({ theme, state: 'focus' }); expect(result).toBe('yellow'); }); test('Get component style with content being a interpolation function', () => { - const result = getComponentStyle('button.hover.interpolatedColor')({ theme }); + const result = getComponentStyle(['button', 'hover', 'interpolatedColor'])({ theme }); expect(result).toBe('someInterpolatedValue'); }); test('Get empty object component style with unknown path', () => { - const result = getComponentStyle('button.unknown.color')({ theme }); + const result = getComponentStyle(['button', 'unknown', 'color'])({ theme }); expect(result).toBeUndefined(); }); test('Get empty object component style with componentStyles being undefined', () => { - const result = getComponentStyle('button.hover.color')({ theme: {} }); + const result = getComponentStyle(['button', 'hover', 'color'])({ theme: {} }); expect(result).toBeUndefined(); }); diff --git a/src/styleHelpers/getComponentStyle.ts b/src/styleHelpers/getComponentStyle.ts index 40a262b..9c82e92 100644 --- a/src/styleHelpers/getComponentStyle.ts +++ b/src/styleHelpers/getComponentStyle.ts @@ -2,25 +2,15 @@ import type { WithTheme } from '@emotion/react'; import { PabloTheme } from '../theme/types'; import { ComponentPath } from '../types'; -const getStringPath = (path: string, props: object) => - path.replace(/\{(.*?)\}/g, (_, val) => props[val] || val).split('.'); - -const getArrayPath =

(path: ComponentPath

, props: object) => - path.map((key) => { - if (typeof key === 'function') { - return key(props as P); - } - - return key; - }); +type FunctionPath

= (props: P) => ComponentPath; export const getComponentStyle =

( - path: ComponentPath

| string, + path: FunctionPath

| ComponentPath, transformFn: (value: unknown) => string | number = (v) => v as string ) => - (props: WithTheme) => { - const pathArray = Array.isArray(path) ? getArrayPath(path, props) : getStringPath(path, props); + (props: WithTheme) => { + const pathArray = typeof path === 'function' ? path(props) : path; const value = pathArray.reduce( (acc, key) => (acc && acc[key]) || undefined, diff --git a/src/theme/types.ts b/src/theme/types.ts index 1a5fcd6..5d4209a 100644 --- a/src/theme/types.ts +++ b/src/theme/types.ts @@ -77,6 +77,9 @@ export interface ComponentStyles { toastCard: ToastCardStyles; } +export type ComponentIdentifier = keyof ComponentStyles; +export type InputComponentIdentifier = 'input' | 'textarea' | 'nativeSelect'; + export interface PabloThemeProviderProps { theme?: RecursivePartial; componentStyles?: RecursivePartial; diff --git a/src/types.ts b/src/types.ts index ce12fb3..ed6138d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import { PabloThemeableProps, Style } from './theme/types'; +import { ComponentStyles, PabloThemeableProps, Style } from './theme/types'; export type SingleOrArray = T | T[]; @@ -18,4 +18,13 @@ export interface BaseProps { export type ComponentPathResolverFn

= (props: P) => string; -export type ComponentPath

= (string | ComponentPathResolverFn

)[]; +export type NonArrayObject = object & { length?: never }; + +type PathTuple = + T extends Record + ? { + [K in keyof T]: T[K] extends NonArrayObject ? [K, ...PathTuple] : [K]; + }[keyof T] + : []; + +export type ComponentPath = PathTuple;