diff --git a/packages/@react-spectrum/s2/src/ActionBar.tsx b/packages/@react-spectrum/s2/src/ActionBar.tsx index e7b65502b36..3bb7745b12f 100644 --- a/packages/@react-spectrum/s2/src/ActionBar.tsx +++ b/packages/@react-spectrum/s2/src/ActionBar.tsx @@ -12,6 +12,7 @@ import {ActionButtonGroup} from './ActionButtonGroup'; import {announce} from 'react-aria/private/live-announcer/LiveAnnouncer'; +import {banner, StyleProps} from './style-utils' with {type: 'macro'}; import {CloseButton} from './CloseButton'; import {ContextValue, SlotProps} from 'react-aria-components/slots'; import { @@ -31,7 +32,6 @@ import {DOMProps, DOMRef, DOMRefValue, Key} from '@react-types/shared'; import {FocusScope} from 'react-aria/FocusScope'; import intlMessages from '../intl/*.json'; import {lightDark, style} from '../style' with {type: 'macro'}; -import {StyleProps} from './style-utils' with {type: 'macro'}; import {useControlledState} from 'react-stately/useControlledState'; import {useDOMRef} from './useDOMRef'; import {useEnterAnimation, useExitAnimation} from 'react-aria/private/utils/animation'; @@ -42,6 +42,7 @@ import {useResizeObserver} from 'react-aria/private/utils/useResizeObserver'; import {useSpectrumContextProps} from './useSpectrumContextProps'; const actionBarStyles = style({ + ...banner(), borderRadius: 'lg', '--s2-container-bg': { type: 'backgroundColor', @@ -60,10 +61,7 @@ const actionBarStyles = style({ isEmphasized: 'transparent', forcedColors: 'ButtonBorder' }, - paddingX: 8, - paddingY: 12, display: 'flex', - gap: 16, alignItems: 'center', position: { isInContainer: 'absolute' @@ -194,7 +192,14 @@ const ActionBarInner = forwardRef(function ActionBarInner( {...keyboardProps} className={ (props.UNSAFE_className || '') + - actionBarStyles({isEmphasized, isInContainer: !!scrollRef, isEntering, isExiting}) + actionBarStyles({ + isEmphasized, + isInContainer: !!scrollRef, + isEntering, + isExiting, + orientation: 'horizontal', + density: 'compact' + }) } style={{ insetInlineEnd: `calc(var(--insetEnd) + ${scrollbarWidth}px)`, diff --git a/packages/@react-spectrum/s2/src/Disclosure.tsx b/packages/@react-spectrum/s2/src/Disclosure.tsx index 1ea0a0baa0c..8ed92cb0ee6 100644 --- a/packages/@react-spectrum/s2/src/Disclosure.tsx +++ b/packages/@react-spectrum/s2/src/Disclosure.tsx @@ -33,6 +33,16 @@ import {CenterBaseline} from './CenterBaseline'; import Chevron from '../ui-icons/Chevron'; import {ContextValue, Provider, useSlottedContext} from 'react-aria-components/slots'; +import { + controlGap, + controlPadding, + verticalPadding, + getAllowedOverrides, + StyleProps, + StylesPropWithFont, + UnsafeStyles, + horizontalPadding +} from './style-utils' with {type: 'macro'}; import { DisclosureStateContext, Disclosure as RACDisclosure, @@ -41,12 +51,6 @@ import { DisclosureProps as RACDisclosureProps } from 'react-aria-components/Disclosure'; import {filterDOMProps} from 'react-aria/filterDOMProps'; -import { - getAllowedOverrides, - StyleProps, - StylesPropWithFont, - UnsafeStyles -} from './style-utils' with {type: 'macro'}; import {Heading} from 'react-aria-components/Heading'; import React, {createContext, forwardRef, ReactNode, useContext} from 'react'; import {useDOMRef} from './useDOMRef'; @@ -189,9 +193,9 @@ const buttonStyles = style( display: 'flex', flexGrow: 1, alignItems: 'baseline', - paddingX: 'calc(self(minHeight) * 3/8 - 1px)', + paddingX: controlPadding(), paddingY: centerPadding(), - gap: 'calc(self(minHeight) * 3/8 - 1px)', + gap: controlPadding(), minHeight: { // compact is equivalent to 'control', but other densities have more padding. size: { @@ -359,16 +363,9 @@ const panelStyles = style({ }); const panelInner = style({ - paddingTop: 8, - paddingBottom: 16, - paddingX: { - size: { - S: 8, - M: space(9), - L: 12, - XL: space(15) - } - } + paddingTop: verticalPadding('L'), + paddingBottom: verticalPadding('2XL'), + paddingX: controlPadding() }); /** diff --git a/packages/@react-spectrum/s2/src/Popover.tsx b/packages/@react-spectrum/s2/src/Popover.tsx index 9fe8b4c1439..bf9a60116fe 100644 --- a/packages/@react-spectrum/s2/src/Popover.tsx +++ b/packages/@react-spectrum/s2/src/Popover.tsx @@ -317,7 +317,7 @@ const innerDivStyle = style( { padding: { padding: { - default: 8, + default: 'popover-padding', none: 0 } }, diff --git a/packages/@react-spectrum/s2/src/RadioGroup.tsx b/packages/@react-spectrum/s2/src/RadioGroup.tsx index 73b521784d8..204405c18ab 100644 --- a/packages/@react-spectrum/s2/src/RadioGroup.tsx +++ b/packages/@react-spectrum/s2/src/RadioGroup.tsx @@ -23,6 +23,7 @@ import {CenterBaseline} from './CenterBaseline'; import {ContextValue} from 'react-aria-components/slots'; import { controlFont, + controlGap, controlSize, field, getAllowedOverrides, @@ -232,7 +233,7 @@ const radioField = style( default: ['max-content', '1fr'], isNoVisibleLabel: ['max-content'] }, - columnGap: 'text-to-control', + columnGap: controlGap(), alignContent: 'start', font: controlFont(), '--field-height': { diff --git a/packages/@react-spectrum/s2/src/style-utils.ts b/packages/@react-spectrum/s2/src/style-utils.ts index d67086de276..f84a2104678 100644 --- a/packages/@react-spectrum/s2/src/style-utils.ts +++ b/packages/@react-spectrum/s2/src/style-utils.ts @@ -236,6 +236,74 @@ const controlSizeS = { } } as const; +// Base Gap +export const controlGap = () => + ({ + default: 'base-gap-medium', + size: { + XS: 'base-gap-extra-small', + S: 'base-gap-small', + L: 'base-gap-large', + XL: 'base-gap-extra-large' + } + }) as const; + +// Base Horizontal Padding - dynamic +export const controlPadding = () => + ({ + default: 'base-padding-horizontal-medium', + size: { + XS: 'base-padding-horizontal-extra-small', + S: 'base-padding-horizontal-small', + L: 'base-padding-horizontal-large', + XL: 'base-padding-horizontal-extra-large', + '2XL': 'base-padding-horizontal-2x-large' + } + }) as const; + +const verticalPaddingTokens = { + XS: 'base-padding-vertical-extra-small', + S: 'base-padding-vertical-small', + M: 'base-padding-vertical-medium', + L: 'base-padding-vertical-large', + XL: 'base-padding-vertical-extra-large', + '2XL': 'base-padding-vertical-2x-large' +} as const; + +const horizontalPaddingTokens = { + XS: 'base-padding-horizontal-extra-small', + S: 'base-padding-horizontal-small', + M: 'base-padding-horizontal-medium', + L: 'base-padding-horizontal-large', + XL: 'base-padding-horizontal-extra-large', + '2XL': 'base-padding-horizontal-2x-large' +} as const; + +// Base Vertical Padding - static +export const verticalPadding = (size: keyof typeof verticalPaddingTokens = 'M') => + verticalPaddingTokens[size]; + +// Base Horizontal Padding - static +export const horizontalPadding = (size: keyof typeof horizontalPaddingTokens = 'M') => + horizontalPaddingTokens[size]; + +export const banner = () => + ({ + paddingX: { + density: { + default: 'banner-padding-horizontal', + compact: 'banner-padding-horizontal-compact' + } + }, + paddingY: 'banner-padding-vertical', + gap: { + orientation: { + horizontal: 'banner-gap-horizontal', + vertical: 'banner-gap-vertical' + } + } + }) as const; + // This generates the border radius for t-shirt sizes using the // Major Second logarithmic scale. export const controlBorderRadius = (size: 'default' | 'sm' = 'default') => @@ -274,8 +342,11 @@ interface ControlResult { height?: ReturnType; display?: 'flex'; alignItems?: 'center' | {default: 'baseline'; [iconOnly]: 'center'}; - columnGap?: 'text-to-visual'; - paddingX?: 'pill' | 'edge-to-text' | {default: 'pill' | 'edge-to-text'; [iconOnly]: 0}; + columnGap?: ReturnType; + paddingX?: + | 'pill' + | ReturnType + | {default: 'pill' | ReturnType; [iconOnly]: 0}; paddingY?: 0 | `[${string}]`; } @@ -286,7 +357,7 @@ const iconOnly = ':has([slot=icon]):not(:has([data-rsp-slot=text]))'; * The text can optionally wrap, aligning the icon with the first line of text. */ export function control(options: ControlOptions): ControlResult { - let paddingX = options.shape === 'pill' ? ('pill' as const) : ('edge-to-text' as const); + let paddingX = options.shape === 'pill' ? ('pill' as const) : controlPadding(); let result: ControlResult = { font: controlFont(), display: 'flex', @@ -303,7 +374,7 @@ export function control(options: ControlOptions): ControlResult { } if (options.icon) { - result.columnGap = 'text-to-visual'; + result.columnGap = controlGap(); result.paddingX = { default: paddingX, [iconOnly]: 0 diff --git a/packages/@react-spectrum/s2/style/spectrum-theme.ts b/packages/@react-spectrum/s2/style/spectrum-theme.ts index 3690d893241..6cec54c2da5 100644 --- a/packages/@react-spectrum/s2/style/spectrum-theme.ts +++ b/packages/@react-spectrum/s2/style/spectrum-theme.ts @@ -37,6 +37,7 @@ import { fontSizeToken, generateOverlayColorScale, getToken, + getSetToken, shadowToken, simpleColorScale, weirdColorToken @@ -427,24 +428,80 @@ export function space(this: MacroContext | void, px: number): string { const relativeSpacing = { // font-size relative values + /** + * @deprecated Use `controlGap()` from `style-utils`, which selects a `base-gap-*` tier per + * component size. + */ 'text-to-control': fontRelative(10), + /** + * @deprecated Use `controlGap()` from `style-utils`, which selects a `base-gap-*` tier per + * component size. + */ 'text-to-visual': { default: fontRelative(6), // -> 5px, 5px, 6px, 7px, 8px touch: fontRelative(8, 17) // -> 6px, 7px, 8px, 9px, 10px, should be 7px, 7px, 8px, 9px, 11px }, // height relative values + /** + * @deprecated Use `controlPadding()` from `style-utils`, which selects a + * `base-padding-horizontal-*` tier per component size. + */ 'edge-to-text': 'calc(self(height, self(minHeight)) * 3 / 8)', pill: 'calc(self(height, self(minHeight)) / 2)' } as const; +// Base gap/padding tiers, selected per component size at the call site +// (see `controlGap`/`controlPadding` in `style-utils`). The token holds the +// desktop value; the `--s2-scale` variable produces the touch value. +const sizeSpacing = { + 'base-gap-extra-small': size(parseFloat(getToken('base-gap-extra-small'))), + 'base-gap-small': size(parseFloat(getToken('base-gap-small'))), + 'base-gap-medium': size(parseFloat(getToken('base-gap-medium'))), + 'base-gap-large': size(parseFloat(getToken('base-gap-large'))), + 'base-gap-extra-large': size(parseFloat(getToken('base-gap-extra-large'))), + 'base-padding-horizontal-extra-small': size( + parseFloat(getSetToken('base-padding-horizontal-extra-small')) + ), + 'base-padding-horizontal-small': size(parseFloat(getSetToken('base-padding-horizontal-small'))), + 'base-padding-horizontal-medium': size(parseFloat(getSetToken('base-padding-horizontal-medium'))), + 'base-padding-horizontal-large': size(parseFloat(getSetToken('base-padding-horizontal-large'))), + 'base-padding-horizontal-extra-large': size( + parseFloat(getSetToken('base-padding-horizontal-extra-large')) + ), + 'base-padding-horizontal-2x-large': size( + parseFloat(getSetToken('base-padding-horizontal-2x-large')) + ), + 'base-padding-vertical-extra-small': size( + parseFloat(getToken('base-padding-vertical-extra-small')) + ), + 'base-padding-vertical-small': size(parseFloat(getToken('base-padding-vertical-small'))), + 'base-padding-vertical-medium': size(parseFloat(getToken('base-padding-vertical-medium'))), + 'base-padding-vertical-large': size(parseFloat(getToken('base-padding-vertical-large'))), + 'base-padding-vertical-extra-large': size( + parseFloat(getToken('base-padding-vertical-extra-large')) + ), + 'base-padding-vertical-2x-large': size(parseFloat(getToken('base-padding-vertical-2x-large'))), + 'banner-gap-horizontal': size(parseFloat(getToken('banner-gap-horizontal'))), + 'banner-gap-vertical': size(parseFloat(getToken('banner-gap-vertical'))), + 'banner-padding-horizontal': size(parseFloat(getToken('banner-padding-horizontal'))), + 'banner-padding-horizontal-compact': size( + parseFloat(getToken('banner-padding-horizontal-compact')) + ), + 'banner-padding-vertical': size(parseFloat(getToken('banner-padding-vertical'))), + 'popover-padding': size(parseFloat(getToken('popover-padding'))), + 'popover-gap': size(parseFloat(getToken('popover-gap'))) +} as const; + const spacing = { ...baseSpacing, - ...relativeSpacing + ...relativeSpacing, + ...sizeSpacing }; const padding = { ...basePadding, - ...relativeSpacing + ...relativeSpacing, + ...sizeSpacing }; /** diff --git a/packages/@react-spectrum/s2/style/tokens.ts b/packages/@react-spectrum/s2/style/tokens.ts index 8f04d400e25..7f4518090dd 100644 --- a/packages/@react-spectrum/s2/style/tokens.ts +++ b/packages/@react-spectrum/s2/style/tokens.ts @@ -27,6 +27,14 @@ export function getToken(name: TokenName): string { return (tokens[name] as any).value; } +// Some dimension tokens store their value under a responsive set (e.g. desktop/mobile) +// rather than a top-level `value`. In S2 the touch scale is applied via the `--s2-scale` +// CSS variable, so we read the desktop value and let that variable produce the touch value. +export function getSetToken(name: TokenName, set: 'desktop' | 'mobile' = 'desktop'): string { + let token = tokens[name] as any; + return token.sets ? token.sets[set].value : token.value; +} + export interface ColorToken { type: 'color'; light: string;