diff --git a/packages/@react-spectrum/s2-ai/exports/MessageSource.ts b/packages/@react-spectrum/s2-ai/exports/MessageSource.ts new file mode 100644 index 00000000000..cba6dfde08a --- /dev/null +++ b/packages/@react-spectrum/s2-ai/exports/MessageSource.ts @@ -0,0 +1,15 @@ +export { + MessageSource, + MessageSourceContext, + SourceList, + SourceListItem, + NumberBadge, + NumberBadgeContext +} from '../src/MessageSource'; + +export type { + MessageSourceProps, + SourceListProps, + SourceListItemProps, + NumberBadgeProps +} from '../src/MessageSource'; diff --git a/packages/@react-spectrum/s2-ai/exports/ResponseStatus.ts b/packages/@react-spectrum/s2-ai/exports/ResponseStatus.ts new file mode 100644 index 00000000000..ed83462f18b --- /dev/null +++ b/packages/@react-spectrum/s2-ai/exports/ResponseStatus.ts @@ -0,0 +1,12 @@ +export { + ResponseStatus, + ResponseStatusContext, + ResponseStatusTitle, + ResponseStatusPanel +} from '../src/ResponseStatus'; + +export type { + ResponseStatusProps, + ResponseStatusTitleProps, + ResponseStatusPanelProps +} from '../src/ResponseStatus'; diff --git a/packages/@react-spectrum/s2-ai/intl/en-US.json b/packages/@react-spectrum/s2-ai/intl/en-US.json new file mode 100644 index 00000000000..8ac04433931 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/intl/en-US.json @@ -0,0 +1,3 @@ +{ + "responsestatus.loading": "Loading" +} diff --git a/packages/@react-spectrum/s2-ai/src/CenterBaseline.tsx b/packages/@react-spectrum/s2-ai/src/CenterBaseline.tsx new file mode 100644 index 00000000000..c7db30f06e5 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/src/CenterBaseline.tsx @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {css} from './style-macro' with {type: 'macro'}; +import {CSSProperties, ReactNode} from 'react'; +import {DOMAttributes} from '@react-types/shared'; +import {filterDOMProps} from 'react-aria/filterDOMProps'; +import {mergeStyles} from './mergeStyles'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {StyleString} from './types'; + +interface CenterBaselineProps extends DOMAttributes { + style?: CSSProperties; + styles?: StyleString; + children: ReactNode; + slot?: string; +} + +const styles = style({ + display: 'flex', + alignItems: 'center' +}); + +export function CenterBaseline(props: CenterBaselineProps): ReactNode { + let domProps = filterDOMProps(props); + return ( +
+ {props.children} +
+ ); +} + +export const centerBaselineBefore = css( + '&::before { content: "\u00a0"; width: 0; visibility: hidden }' +); + +export function centerBaseline( + props: Omit = {} +): (icon: ReactNode) => ReactNode { + return (icon: ReactNode) => {icon}; +} diff --git a/packages/@react-spectrum/s2-ai/src/MessageSource.tsx b/packages/@react-spectrum/s2-ai/src/MessageSource.tsx new file mode 100644 index 00000000000..38ccdb67f3d --- /dev/null +++ b/packages/@react-spectrum/s2-ai/src/MessageSource.tsx @@ -0,0 +1,268 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + AriaLabelingProps, + DOMProps, + DOMRef, + DOMRefValue, + forwardRefType +} from '@react-types/shared'; +import {baseColor, focusRing, style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {ContextValue, SlotProps} from 'react-aria-components/slots'; +import { + Disclosure, + DisclosurePanel, + DisclosurePanelProps, + DisclosureProps, + DisclosureTitle +} from '@react-spectrum/s2/Disclosure'; +import {filterDOMProps} from 'react-aria/filterDOMProps'; +import { + getAllowedOverrides, + StyleProps, + UnsafeStyles +} from './style-utils-copy' with {type: 'macro'}; +import {Link, LinkProps} from 'react-aria-components/Link'; +import {NumberFormatter} from '@internationalized/number'; +import React, {createContext, forwardRef, useContext} from 'react'; +import {useDOMRef} from './useDOMRef'; +import {useLocale} from 'react-aria/I18nProvider'; +import {useSpectrumContextProps} from './useSpectrumContextProps'; + +export interface MessageSourceProps extends Omit { + label: string; +} + +export const MessageSourceContext = + createContext, DOMRefValue>>(null); + +const MessageSourceInternalContext = createContext<{size: 'S' | 'M' | 'L' | 'XL'}>({size: 'M'}); + +/** + * Message sources display references associated with a system message. Associating the source to + * the output builds trust and transparency in the conversation. + */ +export const MessageSource = (forwardRef as forwardRefType)(function MessageSource( + props: MessageSourceProps, + ref: DOMRef +) { + [props, ref] = useSpectrumContextProps(props, ref, MessageSourceContext); + let {label, children, size = 'M', ...otherProps} = props; + + return ( + + + + {label} + {children} + + + + ); +}); + +// SourceList injects a 1-based index so SourceListItem +// can render it without needing an explicit prop. +const SourceListIndexContext = createContext(1); + +const listStyles = style({ + listStyleType: 'none', + padding: 0, + margin: 0, + display: 'flex', + flexDirection: 'column', + gap: 4 +}); + +const itemStyles = style({ + display: 'flex', + alignItems: 'center', + gap: 8 +}); + +export interface SourceListProps extends DisclosurePanelProps {} + +/** + * A SourceList displays an ordered list of sources inside a MessageSource. + * Wrap SourceListItem children inside to have them numbered automatically. + */ +export const SourceList = (forwardRef as forwardRefType)(function SourceList( + props: SourceListProps, + ref: DOMRef +) { + let {children, ...otherProps} = props; + + let numberedChildren = React.Children.map(children, (child, i) => ( + {child} + )); + + return ( + +
    {numberedChildren}
+
+ ); +}); + +const linkStyles = style({ + ...focusRing(), + font: { + size: { + S: 'body-sm', + M: 'body', + L: 'body-lg', + XL: 'body-xl' + } + }, + borderRadius: 'sm', + color: baseColor('neutral'), + disableTapHighlight: true +}); + +export interface SourceListItemProps + extends Omit, UnsafeStyles, DOMProps { + /** The content of the source list item. */ + children: React.ReactNode; +} + +/** + * A SourceListItem represents a single source within a SourceList. + * The item number is provided automatically by the parent SourceList. + */ +export const SourceListItem = (forwardRef as forwardRefType)(function SourceListItem( + props: SourceListItemProps, + ref: DOMRef +) { + let index = useContext(SourceListIndexContext); + let {size} = useContext(MessageSourceInternalContext); + let {children, UNSAFE_style, UNSAFE_className = '', ...otherProps} = props; + let itemRef = useDOMRef(ref); + + return ( +
  • + + + {children} + +
  • + ); +}); + +interface NumberBadgeStyleProps { + /** + * The size of the number badge. + * + * @default 'S' + */ + size?: 'S' | 'M' | 'L' | 'XL'; +} + +export interface NumberBadgeProps + extends DOMProps, AriaLabelingProps, StyleProps, NumberBadgeStyleProps, SlotProps { + /** + * The value to be displayed in the notification badge. + */ + value: number; +} + +interface NumberBadgeContextProps extends Partial {} +export const NumberBadgeContext = + createContext, DOMRefValue>>(null); + +const badge = style( + { + display: 'flex', + color: 'gray-900', + font: { + size: { + S: 'ui-xs', + M: 'ui-sm', + L: 'ui', + XL: 'ui-lg' + } + }, + borderStyle: { + forcedColors: 'solid' + }, + borderWidth: { + forcedColors: '[1px]' + }, + borderColor: { + forcedColors: 'ButtonBorder' + }, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'gray-200', + // These are arbitrary sizes since there are no designs for them + width: { + size: { + S: 14, + M: 16, + L: 18, + XL: 20 + } + }, + height: { + size: { + S: 18, + M: 20, + L: 22, + XL: 24 + } + }, + borderRadius: 'sm' + }, + getAllowedOverrides() +); + +/** + * A small visual indicator showing a count or position. + */ +export const NumberBadge = forwardRef(function NumberBadge( + props: NumberBadgeProps, + ref: DOMRef +) { + [props, ref] = useSpectrumContextProps(props, ref, NumberBadgeContext); + let {size = 'S', value, ...otherProps} = props; + let domRef = useDOMRef(ref); + let {locale} = useLocale(); + let formattedValue = ''; + + if (value <= 0 && process.env.NODE_ENV !== 'production') { + console.warn('Value must be a positive integer'); + } else { + formattedValue = new NumberFormatter(locale).format(value); + } + + // let ariaLabel = props['aria-label'] || undefined; + return ( + + {formattedValue} + + ); +}); diff --git a/packages/@react-spectrum/s2-ai/src/ResponseStatus.tsx b/packages/@react-spectrum/s2-ai/src/ResponseStatus.tsx new file mode 100644 index 00000000000..db566e0571f --- /dev/null +++ b/packages/@react-spectrum/s2-ai/src/ResponseStatus.tsx @@ -0,0 +1,382 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + AriaLabelingProps, + DOMProps, + DOMRef, + DOMRefValue, + GlobalDOMAttributes +} from '@react-types/shared'; +import {baseColor, focusRing, space, style} from '@react-spectrum/s2/style' with {type: 'macro'}; +import {Button} from 'react-aria-components/Button'; +import {CenterBaseline, centerBaseline} from './CenterBaseline'; +import CheckmarkCircle from '@react-spectrum/s2/icons/CheckmarkCircle'; +import Chevron from '../ui-icons/Chevron'; +import {ContextValue, Provider, useSlottedContext} from 'react-aria-components/slots'; +import { + DisclosureStateContext, + Disclosure as RACDisclosure, + DisclosurePanel as RACDisclosurePanel, + DisclosurePanelProps as RACDisclosurePanelProps, + DisclosureProps as RACDisclosureProps +} from 'react-aria-components/Disclosure'; +import {filterDOMProps} from 'react-aria/filterDOMProps'; +import { + getAllowedOverrides, + StyleProps, + StylesPropWithFont, + UnsafeStyles +} from './style-utils-copy' with {type: 'macro'}; +import {Heading} from 'react-aria-components/Heading'; +import {IconContext} from '@react-spectrum/s2/Icon'; +// @ts-ignore +import intlMessages from '../intl/*.json'; +import {ProgressCircle} from '@react-spectrum/s2/ProgressCircle'; +import React, {createContext, forwardRef, ReactNode, useContext} from 'react'; +import {useDOMRef} from './useDOMRef'; +import {useLocale} from 'react-aria/I18nProvider'; +import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter'; +import {useSpectrumContextProps} from './useSpectrumContextProps'; + +export interface ResponseStatusProps + extends + Omit< + RACDisclosureProps, + 'className' | 'style' | 'render' | 'children' | keyof GlobalDOMAttributes + >, + StyleProps { + /** + * The size of the response status. + * + * @default 'M' + */ + size?: 'S' | 'M' | 'L' | 'XL'; + /** + * The amount of space between stacked response statuses. + * + * @default 'regular' + */ + density?: 'compact' | 'regular' | 'spacious'; + /** + * Whether the response is still being generated. When true, a ProgressCircle replaces + * the chevron and the panel cannot be expanded. The trigger remains focusable. + */ + isLoading?: boolean; + /** + * The contents of the response status, consisting of a ResponseStatusTitle and + * ResponseStatusPanel. + */ + children: ReactNode; +} + +export const ResponseStatusContext = + createContext, DOMRefValue>>(null); + +const responseStatus = style( + { + color: 'heading', + minWidth: 200 + }, + getAllowedOverrides() +); + +/** + * A ResponseStatus indicates the progress of a system response while it is begin generated and when + * it is complete. + */ +export const ResponseStatus = forwardRef(function ResponseStatus( + props: ResponseStatusProps, + ref: DOMRef +) { + [props, ref] = useSpectrumContextProps(props, ref, ResponseStatusContext); + let {size = 'M', density = 'regular', isLoading, UNSAFE_style, UNSAFE_className = ''} = props; + let domRef = useDOMRef(ref); + + let disclosureProps: Partial = {}; + if (isLoading) { + disclosureProps.isExpanded = false; + disclosureProps.onExpandedChange = () => {}; + } + + return ( + + + {props.children} + + + ); +}); + +export interface ResponseStatusTitleProps extends UnsafeStyles, DOMProps { + /** + * The heading level of the response status header. + * + * @default 3 + */ + level?: number; + /** The contents of the response status header. */ + children: React.ReactNode; + /** + * Spectrum-defined styles, returned by the `style()` macro. Only allows overriding + * `font`, `fontFamily`, `fontWeight`, `fontSize`, and `lineHeight`. + */ + styles?: StylesPropWithFont; +} + +const headingStyle = style({ + margin: 0, + flexGrow: 1, + display: 'flex', + flexShrink: 1, + minWidth: 0 +}); + +const buttonStyles = style( + { + ...focusRing(), + outlineOffset: -2, + font: { + size: { + S: 'body-sm', + M: 'body', + L: 'body-lg', + XL: 'body-xl' + } + }, + color: { + default: baseColor('neutral'), + forcedColors: 'ButtonText', + isDisabled: { + default: 'disabled', + forcedColors: 'GrayText' + } + }, + display: 'flex', + flexGrow: 1, + alignItems: 'center', + paddingX: 'calc(self(minHeight) * 3/8 - 1px)', + gap: 'calc(self(minHeight) * 3/8 - 1px)', + minHeight: { + size: { + S: { + density: { + compact: 18, + regular: 24, + spacious: 32 + } + }, + M: { + density: { + compact: 24, + regular: 32, + spacious: 40 + } + }, + L: { + density: { + compact: 32, + regular: 40, + spacious: 48 + } + }, + XL: { + density: { + compact: 40, + regular: 48, + spacious: 56 + } + } + } + }, + width: 'full', + backgroundColor: 'transparent', + transition: 'default', + borderWidth: 0, + borderRadius: 'default', + textAlign: 'start', + disableTapHighlight: true + }, + getAllowedOverrides({font: true}) +); + +const chevronStyles = style({ + rotate: { + isRTL: 180, + isExpanded: 90 + }, + transition: 'default', + '--iconPrimary': { + type: 'fill', + value: 'currentColor' + }, + flexShrink: 0 +}); + +const progressCircleStyles = style({ + width: { + size: { + S: 16, + M: 18, + L: 20, + XL: 22 + } + }, + height: { + size: { + S: 16, + M: 18, + L: 20, + XL: 22 + } + } +}); + +/** + * A response status title consisting of a heading and a trigger button. The leading icon is + * a progress circle while loading and a chevron once complete; a checkmark is rendered at + * the trailing edge of the row when not loading. + */ +export const ResponseStatusTitle = forwardRef(function ResponseStatusTitle( + props: ResponseStatusTitleProps, + ref: DOMRef +) { + let {level = 3, UNSAFE_style, UNSAFE_className = '', styles, ...otherProps} = props; + let domRef = useDOMRef(ref); + const domProps = filterDOMProps(otherProps); + let {direction} = useLocale(); + let {isExpanded} = useContext(DisclosureStateContext)!; + let {size = 'M', density, isLoading} = useSlottedContext(ResponseStatusContext)!; + let isRTL = direction === 'rtl'; + let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2'); + + return ( + + + + ); +}); + +export interface ResponseStatusPanelProps + extends + Omit, + UnsafeStyles, + DOMProps, + AriaLabelingProps { + children: React.ReactNode; +} + +const panelStyles = style({ + font: 'body', + height: '--disclosure-panel-height', + overflow: 'clip', + transition: { + default: '[height]', + '@media (prefers-reduced-motion: reduce)': 'none' + } +}); + +const panelInner = style({ + paddingTop: 8, + paddingBottom: 16, + paddingX: { + size: { + S: 8, + M: space(9), + L: 12, + XL: space(15) + } + } +}); + +/** + * A response status panel is a collapsible section of content that is hidden until the + * response status is expanded. The panel cannot be expanded while `isLoading` is true. + */ +export const ResponseStatusPanel = forwardRef(function ResponseStatusPanel( + props: ResponseStatusPanelProps, + ref: DOMRef +) { + let {UNSAFE_style, UNSAFE_className = '', ...otherProps} = props; + let {size = 'M'} = useSlottedContext(ResponseStatusContext)!; + const domProps = filterDOMProps(otherProps); + let panelRef = useDOMRef(ref); + return ( + +
    {props.children}
    +
    + ); +}); diff --git a/packages/@react-spectrum/s2-ai/src/properties.json b/packages/@react-spectrum/s2-ai/src/properties.json new file mode 100644 index 00000000000..b026a1fc374 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/src/properties.json @@ -0,0 +1,2193 @@ +{ + "properties": { + "--iconPrimary": "a", + "--s2-container-bg": "b", + "--translateX": "c", + "--translateY": "d", + "alignItems": "e", + "aspectRatio": "f", + "backgroundColor": "g", + "borderBottomWidth": "h", + "borderColor": "i", + "borderEndEndRadius": "j", + "borderEndStartRadius": "k", + "borderInlineEndWidth": "l", + "borderInlineStartWidth": "m", + "borderStartEndRadius": "n", + "borderStartStartRadius": "o", + "color": "p", + "columnGap": "q", + "cursor": "r", + "display": "s", + "fill": "t", + "fontFamily": "u", + "fontSize": "v", + "fontVariationSettings": "w", + "fontWeight": "x", + "gridColumnEnd": "y", + "gridColumnStart": "z", + "gridRowEnd": "A", + "gridRowStart": "B", + "gridTemplateAreas": "C", + "gridTemplateColumns": "D", + "gridTemplateRows": "E", + "height": "F", + "marginBottom": "G", + "marginInlineEnd": "H", + "marginInlineStart": "I", + "marginTop": "J", + "maxHeight": "K", + "maxWidth": "L", + "minHeight": "M", + "minWidth": "N", + "outlineColor": "O", + "overflowY": "P", + "paddingBottom": "Q", + "paddingInlineEnd": "R", + "paddingInlineStart": "S", + "paddingTop": "T", + "rowGap": "U", + "stroke": "V", + "top": "W", + "transitionDuration": "X", + "transitionProperty": "Y", + "width": "Z", + "--scaleX": "_a", + "--scaleY": "_b", + "alignContent": "_c", + "alignSelf": "_d", + "animationDuration": "_e", + "animationFillMode": "_f", + "animationName": "_g", + "animationTimingFunction": "_h", + "backgroundImage": "_i", + "borderStyle": "_j", + "borderTopWidth": "_k", + "bottom": "_l", + "boxDecorationBreak": "_m", + "boxShadow": "_n", + "boxSizing": "_o", + "colorScheme": "_p", + "contain": "_q", + "filter": "_r", + "flexBasis": "_s", + "flexDirection": "_t", + "flexGrow": "_u", + "flexShrink": "_v", + "flexWrap": "_w", + "fontSynthesisWeight": "_x", + "forcedColorAdjust": "_y", + "insetInlineEnd": "_z", + "insetInlineStart": "_A", + "isolation": "_B", + "justifyContent": "_C", + "justifyItems": "_D", + "left": "_E", + "lineHeight": "_F", + "listStyleType": "_G", + "objectFit": "_H", + "opacity": "_I", + "order": "_J", + "outlineOffset": "_K", + "outlineStyle": "_L", + "outlineWidth": "_M", + "overflowX": "_N", + "pointerEvents": "_O", + "position": "_P", + "resize": "_Q", + "right": "_R", + "rotate": "_S", + "scrollPaddingInlineStart": "_T", + "scrollPaddingTop": "_U", + "strokeWidth": "_V", + "textAlign": "_W", + "textDecoration": "_X", + "textOverflow": "_Y", + "transform": "_Z", + "transformOrigin": "_0", + "transitionDelay": "_1", + "transitionTimingFunction": "_2", + "unicodeBidi": "_3", + "userSelect": "_4", + "verticalAlign": "_5", + "visibility": "_6", + "whiteSpace": "_7", + "willChange": "_8", + "zIndex": "_9", + "-webkit-box-orient": "__a", + "-webkit-line-clamp": "__b", + "-webkit-tap-highlight-color": "__c", + "animationDirection": "__d", + "animationIterationCount": "__e", + "appearance": "__f", + "backgroundAttachment": "__g", + "backgroundBlendMode": "__h", + "backgroundClip": "__i", + "backgroundOrigin": "__j", + "backgroundPosition": "__k", + "backgroundRepeat": "__l", + "backgroundSize": "__m", + "borderCollapse": "__n", + "borderSpacing": "__o", + "breakAfter": "__p", + "breakBefore": "__q", + "breakInside": "__r", + "captionSide": "__s", + "clear": "__t", + "containIntrinsicHeight": "__u", + "containIntrinsicWidth": "__v", + "float": "__w", + "gridAutoFlow": "__x", + "gridAutoRows": "__y", + "hyphens": "__z", + "justifySelf": "__A", + "listStylePosition": "__B", + "mixBlendMode": "__C", + "objectPosition": "__D", + "overflow": "__E", + "overscrollBehaviorX": "__F", + "overscrollBehaviorY": "__G", + "scale": "__H", + "scrollBehavior": "__I", + "scrollMarginBottom": "__J", + "scrollMarginInlineEnd": "__K", + "scrollMarginInlineStart": "__L", + "scrollMarginTop": "__M", + "scrollPaddingBottom": "__N", + "scrollPaddingInlineEnd": "__O", + "scrollSnapAlign": "__P", + "scrollSnapStop": "__Q", + "scrollSnapType": "__R", + "tableLayout": "__S", + "textIndent": "__T", + "textTransform": "__U", + "textUnderlineOffset": "__V", + "textWrap": "__W", + "touchAction": "__X", + "translate": "__Y", + "wordBreak": "__Z", + "--fs": "__0" + }, + "values": { + "--iconPrimary": { + "ButtonText": "a", + "GrayText": "b", + "Highlight": "c", + "HighlightText": "d", + "black": "e", + "currentColor": "f", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.21)": "g", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.85)": "h", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.94)": "i", + "light-dark(rgb(143, 143, 143), rgb(138, 138, 138))": "j", + "light-dark(rgb(19, 19, 19), rgb(242, 242, 242))": "k", + "light-dark(rgb(198, 198, 198), rgb(68, 68, 68))": "l", + "light-dark(rgb(212, 91, 0), rgb(224, 100, 0))": "m", + "light-dark(rgb(240, 56, 35), rgb(252, 67, 46))": "n", + "light-dark(rgb(255, 255, 255), rgb(17, 17, 17))": "o", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "p", + "light-dark(rgb(59, 99, 251), rgb(86, 129, 255))": "q", + "light-dark(rgb(7, 147, 85), rgb(9, 157, 89))": "r", + "light-dark(rgb(75, 117, 255), rgb(86, 129, 255))": "s", + "white": "t" + }, + "--s2-container-bg": { + "ButtonFace": "a", + "black": "b", + "light-dark(rgb(248, 248, 248), rgb(27, 27, 27))": "c", + "light-dark(rgb(255, 255, 255), rgb(17, 17, 17))": "d", + "light-dark(rgb(255, 255, 255), rgb(34, 34, 34))": "e", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "f", + "white": "g" + }, + "--translateX": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-25%": "e", + "-28px": "f", + "-2px": "g", + "-32px": "h", + "-36px": "i", + "-40px": "j", + "-44px": "k", + "-48px": "l", + "-4px": "m", + "-56px": "n", + "-64px": "o", + "-80px": "p", + "-8px": "q", + "-96px": "r", + "0px": "s", + "100%": "t", + "12px": "u", + "16px": "v", + "20px": "w", + "24px": "x", + "25%": "y", + "28px": "z", + "2px": "A", + "32px": "B", + "36px": "C", + "40px": "D", + "44px": "E", + "48px": "F", + "4px": "G", + "56px": "H", + "64px": "I", + "80px": "J", + "8px": "K", + "96px": "L" + }, + "--translateY": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-50%": "m", + "-56px": "n", + "-64px": "o", + "-80px": "p", + "-8px": "q", + "-96px": "r", + "0px": "s", + "100%": "t", + "12px": "u", + "16px": "v", + "20px": "w", + "24px": "x", + "28px": "y", + "2px": "z", + "32px": "A", + "36px": "B", + "40px": "C", + "44px": "D", + "48px": "E", + "4px": "F", + "56px": "G", + "64px": "H", + "80px": "I", + "8px": "J", + "96px": "K" + }, + "alignItems": { + "baseline": "a", + "center": "b", + "end": "c", + "start": "d", + "stretch": "e" + }, + "aspectRatio": { + "1/1": "a", + "3/1": "b", + "3/2": "c", + "5/1": "d" + }, + "backgroundColor": { + "Background": "a", + "ButtonFace": "b", + "ButtonText": "c", + "GrayText": "d", + "Highlight": "e", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.11)": "f", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.14)": "g", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.17)": "h", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.85)": "i", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.94)": "j", + "light-dark(rgb(11, 120, 179), rgb(13, 125, 186))": "k", + "light-dark(rgb(113, 85, 250), rgb(116, 91, 252))": "l", + "light-dark(rgb(114, 114, 114), rgb(118, 118, 118))": "m", + "light-dark(rgb(139, 109, 66), rgb(143, 114, 69))": "n", + "light-dark(rgb(154, 71, 226), rgb(157, 78, 228))": "o", + "light-dark(rgb(163, 196, 0), rgb(136, 164, 0))": "p", + "light-dark(rgb(170, 94, 56), rgb(176, 98, 59))": "q", + "light-dark(rgb(181, 57, 200), rgb(186, 60, 206))": "r", + "light-dark(rgb(183, 40, 24), rgb(252, 67, 46))": "s", + "light-dark(rgb(183, 40, 24), rgb(255, 103, 86))": "t", + "light-dark(rgb(19, 19, 19), rgb(242, 242, 242))": "u", + "light-dark(rgb(197, 255, 156), rgb(21, 51, 1))": "v", + "light-dark(rgb(198, 198, 198), rgb(68, 68, 68))": "w", + "light-dark(rgb(206, 42, 146), rgb(213, 45, 151))": "x", + "light-dark(rgb(209, 245, 245), rgb(0, 49, 54))": "y", + "light-dark(rgb(211, 246, 234), rgb(0, 50, 44))": "z", + "light-dark(rgb(215, 247, 225), rgb(0, 51, 38))": "A", + "light-dark(rgb(215, 50, 32), rgb(223, 52, 34))": "B", + "light-dark(rgb(217, 244, 253), rgb(0, 48, 65))": "C", + "light-dark(rgb(217, 35, 97), rgb(224, 38, 101))": "D", + "light-dark(rgb(218, 218, 218), rgb(57, 57, 57))": "E", + "light-dark(rgb(225, 225, 225), rgb(50, 50, 50))": "F", + "light-dark(rgb(229, 240, 254), rgb(12, 33, 117))": "G", + "light-dark(rgb(233, 233, 233), rgb(44, 44, 44))": "H", + "light-dark(rgb(233, 233, 233), rgb(57, 57, 57))": "I", + "light-dark(rgb(234, 246, 173), rgb(39, 47, 0))": "J", + "light-dark(rgb(235, 238, 255), rgb(47, 0, 140))": "K", + "light-dark(rgb(243, 243, 243), rgb(34, 34, 34))": "L", + "light-dark(rgb(244, 235, 252), rgb(64, 0, 122))": "M", + "light-dark(rgb(245, 199, 0), rgb(218, 159, 0))": "N", + "light-dark(rgb(247, 238, 225), rgb(58, 40, 14))": "O", + "light-dark(rgb(249, 236, 229), rgb(79, 28, 7))": "P", + "light-dark(rgb(252, 125, 0), rgb(224, 100, 0))": "Q", + "light-dark(rgb(253, 233, 255), rgb(79, 0, 95))": "R", + "light-dark(rgb(255, 232, 240), rgb(93, 0, 34))": "S", + "light-dark(rgb(255, 232, 247), rgb(90, 0, 57))": "T", + "light-dark(rgb(255, 235, 232), rgb(87, 17, 7))": "U", + "light-dark(rgb(255, 236, 207), rgb(80, 27, 0))": "V", + "light-dark(rgb(255, 241, 151), rgb(61, 39, 0))": "W", + "light-dark(rgb(255, 255, 255), rgb(17, 17, 17))": "X", + "light-dark(rgb(39, 77, 234), rgb(105, 149, 254))": "Y", + "light-dark(rgb(39, 77, 234), rgb(86, 129, 255))": "Z", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "0", + "light-dark(rgb(5, 131, 78), rgb(6, 136, 80))": "1", + "light-dark(rgb(59, 99, 251), rgb(64, 105, 253))": "2", + "light-dark(rgb(59, 99, 251), rgb(86, 129, 255))": "3", + "light-dark(rgb(7, 129, 109), rgb(8, 134, 112))": "4", + "light-dark(rgb(8, 126, 137), rgb(9, 131, 142))": "5", + "light-dark(rgb(80, 80, 80), rgb(109, 109, 109))": "6", + "light-dark(rgb(93, 180, 31), rgb(78, 154, 23))": "7", + "rgba(0, 0, 0, 0.44)": "8", + "transparent": "9", + "ButtonBorder": "_a", + "Mark": "_b", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 1)": "_c", + "light-dark(rgb(215, 50, 32), rgb(252, 67, 46))": "_d", + "light-dark(rgb(229, 240, 254), rgb(15, 28, 82))": "_e", + "light-dark(rgb(239, 239, 239), rgb(44, 44, 44))": "_f", + "light-dark(rgb(255, 255, 255), rgb(34, 34, 34))": "_g", + "light-dark(rgb(75, 117, 255), rgb(64, 105, 253))": "_h", + "light-dark(rgb(80, 80, 80), rgb(175, 175, 175))": "_i" + }, + "borderBottomWidth": { + "0px": "a", + "1px": "b", + "2px": "c", + "4px": "d" + }, + "borderColor": { + "ButtonBorder": "a", + "ButtonText": "b", + "GrayText": "c", + "Highlight": "d", + "Mark": "e", + "black": "f", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.17)": "g", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.21)": "h", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.85)": "i", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.94)": "j", + "light-dark(rgb(0, 0, 0), rgb(255, 255, 255))": "k", + "light-dark(rgb(11, 164, 93), rgb(4, 124, 75))": "l", + "light-dark(rgb(183, 40, 24), rgb(255, 103, 86))": "m", + "light-dark(rgb(19, 19, 19), rgb(242, 242, 242))": "n", + "light-dark(rgb(198, 198, 198), rgb(68, 68, 68))": "o", + "light-dark(rgb(215, 50, 32), rgb(252, 67, 46))": "p", + "light-dark(rgb(218, 218, 218), rgb(57, 57, 57))": "q", + "light-dark(rgb(225, 225, 225), rgb(50, 50, 50))": "r", + "light-dark(rgb(232, 106, 0), rgb(185, 73, 0))": "s", + "light-dark(rgb(240, 56, 35), rgb(223, 52, 34))": "t", + "light-dark(rgb(39, 77, 234), rgb(105, 149, 254))": "u", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "v", + "light-dark(rgb(59, 99, 251), rgb(86, 129, 255))": "w", + "light-dark(rgb(75, 117, 255), rgb(64, 105, 253))": "x", + "light-dark(rgb(80, 80, 80), rgb(175, 175, 175))": "y", + "rgb(from light-dark(rgb(0, 0, 0), rgb(255, 255, 255)) r g b / 42%)": "z", + "transparent": "A", + "white": "B" + }, + "borderEndEndRadius": { + "0.25rem": "a", + "0.5rem": "b", + "0.625rem": "c", + "0px": "d", + "1rem": "e", + "6px": "f", + "9999px": "g", + "calc(var(--F, var(--M, 9999px)) / 2)": "h", + "inherit": "i", + "round(var(--radius) * var(--size), 1px)": "j" + }, + "borderEndStartRadius": { + "0.25rem": "a", + "0.5rem": "b", + "0.625rem": "c", + "0px": "d", + "1rem": "e", + "6px": "f", + "9999px": "g", + "calc(var(--F, var(--M, 9999px)) / 2)": "h", + "inherit": "i", + "round(var(--radius) * var(--size), 1px)": "j" + }, + "borderInlineEndWidth": { + "0px": "a", + "1px": "b", + "2px": "c", + "4px": "d" + }, + "borderInlineStartWidth": { + "0px": "a", + "1px": "b", + "2px": "c", + "4px": "d" + }, + "borderStartEndRadius": { + "0.25rem": "a", + "0.5rem": "b", + "0.625rem": "c", + "0px": "d", + "1rem": "e", + "6px": "f", + "9999px": "g", + "calc(var(--F, var(--M, 9999px)) / 2)": "h", + "inherit": "i", + "round(var(--radius) * var(--size), 1px)": "j" + }, + "borderStartStartRadius": { + "0.25rem": "a", + "0.5rem": "b", + "0.625rem": "c", + "0px": "d", + "1rem": "e", + "6px": "f", + "9999px": "g", + "calc(var(--F, var(--M, 9999px)) / 2)": "h", + "inherit": "i", + "round(var(--radius) * var(--size), 1px)": "j" + }, + "color": { + "ButtonFace": "a", + "ButtonText": "b", + "GrayText": "c", + "Highlight": "d", + "HighlightText": "e", + "LinkText": "f", + "black": "g", + "lch(from var(--g, var(--s2-container-bg)) calc((49.44 - l) * infinity) 0 0 / 1)": "h", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.21)": "i", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.85)": "j", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.94)": "k", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 1)": "l", + "light-dark(rgb(0, 0, 0), rgb(255, 255, 255))": "m", + "light-dark(rgb(113, 113, 113), rgb(138, 138, 138))": "n", + "light-dark(rgb(19, 19, 19), rgb(242, 242, 242))": "o", + "light-dark(rgb(198, 198, 198), rgb(68, 68, 68))": "p", + "light-dark(rgb(215, 50, 32), rgb(252, 67, 46))": "q", + "light-dark(rgb(255, 255, 255), rgb(17, 17, 17))": "r", + "light-dark(rgb(39, 77, 234), rgb(105, 149, 254))": "s", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "t", + "light-dark(rgb(59, 99, 251), rgb(86, 129, 255))": "u", + "light-dark(rgb(80, 80, 80), rgb(175, 175, 175))": "v", + "transparent": "w", + "white": "x" + }, + "columnGap": { + "0.125rem": "a", + "0.25rem": "b", + "0.42857142857142855em": "c", + "0.47058823529411764em": "d", + "0.5rem": "e", + "0.7142857142857143em": "f", + "0.75rem": "g", + "0rem": "h", + "1.25rem": "i", + "1.5rem": "j", + "1.75rem": "k", + "1rem": "l", + "2.25rem": "m", + "2.5rem": "n", + "2.75rem": "o", + "2rem": "p", + "3.5rem": "q", + "3rem": "r", + "4rem": "s", + "5rem": "t", + "6rem": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "cursor": { + "alias": "a", + "all-scroll": "b", + "auto": "c", + "cell": "d", + "col-resize": "e", + "context-menu": "f", + "copy": "g", + "crosshair": "h", + "default": "i", + "e-resize": "j", + "ew-resize": "k", + "grab": "l", + "grabbing": "m", + "help": "n", + "move": "o", + "n-resize": "p", + "ne-resize": "q", + "nesw-resize": "r", + "no-drop": "s", + "none": "t", + "not-allowed": "u", + "ns-resize": "v", + "nw-resize": "w", + "nwse-resize": "x", + "pointer": "y", + "progress": "z", + "row-resize": "A", + "s-resize": "B", + "se-resize": "C", + "text": "D", + "vertical-text": "E", + "w-resize": "F", + "wait": "G", + "zoom-in": "H", + "zoom-out": "I" + }, + "display": { + "-webkit-box": "a", + "block": "b", + "contents": "c", + "flex": "d", + "grid": "e", + "inline": "f", + "inline-block": "g", + "inline-flex": "h", + "inline-grid": "i", + "list-item": "j", + "none": "k" + }, + "fill": { + "Highlight": "a", + "light-dark(rgb(10, 141, 153), rgb(11, 151, 164))": "b", + "light-dark(rgb(11, 162, 134), rgb(8, 134, 112))": "c", + "light-dark(rgb(122, 106, 253), rgb(128, 119, 254))": "d", + "light-dark(rgb(128, 128, 128), rgb(137, 137, 137))": "e", + "light-dark(rgb(143, 143, 143), rgb(138, 138, 138))": "f", + "light-dark(rgb(143, 172, 0), rgb(122, 147, 0))": "g", + "light-dark(rgb(154, 123, 77), rgb(163, 132, 84))": "h", + "light-dark(rgb(166, 92, 231), rgb(173, 105, 233))": "i", + "light-dark(rgb(184, 109, 70), rgb(192, 119, 80))": "j", + "light-dark(rgb(200, 68, 220), rgb(213, 73, 235))": "k", + "light-dark(rgb(210, 149, 0), rgb(218, 159, 0))": "l", + "light-dark(rgb(212, 91, 0), rgb(224, 100, 0))": "m", + "light-dark(rgb(225, 225, 225), rgb(50, 50, 50))": "n", + "light-dark(rgb(228, 52, 163), rgb(236, 67, 175))": "o", + "light-dark(rgb(240, 45, 110), rgb(255, 51, 119))": "p", + "light-dark(rgb(240, 56, 35), rgb(252, 67, 46))": "q", + "light-dark(rgb(41, 41, 41), rgb(219, 219, 219))": "r", + "light-dark(rgb(48, 167, 254), rgb(24, 142, 220))": "s", + "light-dark(rgb(7, 147, 85), rgb(9, 157, 89))": "t", + "light-dark(rgb(75, 117, 255), rgb(86, 129, 255))": "u", + "light-dark(rgb(82, 161, 25), rgb(66, 134, 18))": "v", + "white": "w" + }, + "fontFamily": { + "adobe-clean-han-japanese, 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', Osaka, YuGothic, 'Yu Gothic', 'メイリオ', Meiryo, 'MS Pゴシック', 'MS PGothic', sans-serif": "a", + "adobe-clean-han-korean, source-han-korean, 'Malgun Gothic', 'Apple Gothic', sans-serif": "b", + "adobe-clean-han-simplified-c, source-han-simplified-c, 'SimSun', 'Heiti SC Light', sans-serif": "c", + "adobe-clean-han-traditional, source-han-traditional, 'MingLiu', 'Heiti TC Light', sans-serif": "d", + "adobe-clean-han-traditional, source-han-traditional, 'MingLiu', 'Microsoft JhengHei UI', 'Microsoft JhengHei', 'Heiti TC Light', sans-serif": "e", + "adobe-clean-serif, \"Source Serif\", Georgia, serif": "f", + "adobe-clean-variable, adobe-clean, ui-sans-serif, system-ui, sans-serif": "g", + "myriad-arabic, ui-sans-serif, system-ui, sans-serif": "h", + "myriad-hebrew, ui-sans-serif, system-ui, sans-serif": "i", + "source-code-pro, \"Source Code Pro\", Monaco, monospace": "j", + "var(--s2-font-family-sans, adobe-clean-spectrum-vf), adobe-clean-variable, adobe-clean, ui-sans-serif, system-ui, sans-serif": "k", + "adobe-clean-han-hong-kong, source-han-hong-kong, 'MingLiu', 'Microsoft JhengHei UI', 'Microsoft JhengHei', 'Heiti TC Light', sans-serif": "l" + }, + "fontSize": { + "0.6875rem": "a", + "0.75rem": "b", + "0.8125rem": "c", + "0.875rem": "d", + "0.9375rem": "e", + "1.0625rem": "f", + "1.125rem": "g", + "1.1875rem": "h", + "1.25rem": "i", + "1.375rem": "j", + "1.5625rem": "k", + "1.5rem": "l", + "1.6875rem": "m", + "1.75rem": "n", + "1.9375rem": "o", + "1rem": "p", + "2.125rem": "q", + "2.25rem": "r", + "2.75rem": "s", + "2.8125rem": "t", + "3.4375rem": "u", + "3.625rem": "v", + "4.375rem": "w", + "round(var(--s2-font-size-base, 14) * var(--fs) / 16 * 1rem, 1px)": "x" + }, + "fontVariationSettings": { + "\"wght\" 300": "a", + "\"wght\" 400": "b", + "\"wght\" 500": "c", + "\"wght\" 700": "d", + "\"wght\" 800": "e" + }, + "fontWeight": { + "300": "a", + "400": "b", + "500": "c", + "600": "g", + "700": "d", + "800": "e", + "900": "f" + }, + "height": { + "0px": "a", + "100%": "b", + "100vh": "c", + "auto": "d", + "calc(0.25rem * var(--s2-scale))": "e", + "calc(0.3125rem * var(--s2-scale))": "f", + "calc(0.375rem * var(--s2-scale))": "g", + "calc(0.5625rem * var(--s2-scale))": "h", + "calc(0.5rem * var(--s2-scale))": "i", + "calc(0.625rem * var(--s2-scale))": "j", + "calc(0.75rem * var(--s2-scale))": "k", + "calc(0.875rem * var(--s2-scale))": "l", + "calc(1.125rem * var(--s2-scale))": "m", + "calc(1.25rem * var(--s2-scale))": "n", + "calc(1.375rem * var(--s2-scale))": "o", + "calc(1.5rem * var(--s2-scale))": "p", + "calc(1.625rem * var(--s2-scale))": "q", + "calc(10rem * var(--s2-scale))": "r", + "calc(12rem * var(--s2-scale))": "s", + "calc(1rem * var(--s2-scale))": "t", + "calc(2.25rem * var(--s2-scale))": "u", + "calc(2.5rem * var(--s2-scale))": "v", + "calc(2.75rem * var(--s2-scale))": "w", + "calc(2rem * var(--s2-scale))": "x", + "calc(3.5rem * var(--s2-scale))": "y", + "calc(3rem * var(--s2-scale))": "z", + "calc(4rem * var(--s2-scale))": "A", + "calc(6rem * var(--s2-scale))": "B", + "calc(8.75rem * var(--s2-scale))": "C", + "fit-content": "D", + "max-content": "E", + "min-content": "F" + }, + "marginBottom": { + "-0.125rem": "a", + "-0.25rem": "b", + "-0.5rem": "c", + "-0.75rem": "d", + "-1.25rem": "e", + "-1.5rem": "f", + "-1.75rem": "g", + "-1rem": "h", + "-2.25rem": "i", + "-2.5rem": "j", + "-2.75rem": "k", + "-2rem": "l", + "-3.5rem": "m", + "-3rem": "n", + "-4rem": "o", + "-5rem": "p", + "-6rem": "q", + "0.125rem": "r", + "0.25rem": "s", + "0.42857142857142855em": "t", + "0.47058823529411764em": "u", + "0.5rem": "v", + "0.7142857142857143em": "w", + "0.75rem": "x", + "0rem": "y", + "1.25rem": "z", + "1.5rem": "A", + "1.75rem": "B", + "1rem": "C", + "2.25rem": "D", + "2.5rem": "E", + "2.75rem": "F", + "2rem": "G", + "3.5rem": "H", + "3rem": "I", + "4rem": "J", + "5rem": "K", + "6rem": "L", + "auto": "M", + "calc(var(--F, var(--M)) * 3 / 8)": "N", + "calc(var(--F, var(--M)) / 2)": "O" + }, + "marginInlineEnd": { + "-0.125rem": "a", + "-0.25rem": "b", + "-0.5rem": "c", + "-0.75rem": "d", + "-1.25rem": "e", + "-1.5rem": "f", + "-1.75rem": "g", + "-1rem": "h", + "-2.25rem": "i", + "-2.5rem": "j", + "-2.75rem": "k", + "-2rem": "l", + "-3.5rem": "m", + "-3rem": "n", + "-4rem": "o", + "-5rem": "p", + "-6rem": "q", + "0.125rem": "r", + "0.25rem": "s", + "0.42857142857142855em": "t", + "0.47058823529411764em": "u", + "0.5rem": "v", + "0.7142857142857143em": "w", + "0.75rem": "x", + "0rem": "y", + "1.25rem": "z", + "1.5rem": "A", + "1.75rem": "B", + "1rem": "C", + "2.25rem": "D", + "2.5rem": "E", + "2.75rem": "F", + "2rem": "G", + "3.5rem": "H", + "3rem": "I", + "4rem": "J", + "5rem": "K", + "6rem": "L", + "auto": "M", + "calc(var(--F, var(--M)) * 3 / 8)": "N", + "calc(var(--F, var(--M)) / 2)": "O" + }, + "marginInlineStart": { + "-0.125rem": "a", + "-0.25rem": "b", + "-0.5rem": "c", + "-0.75rem": "d", + "-1.25rem": "e", + "-1.5rem": "f", + "-1.75rem": "g", + "-1rem": "h", + "-2.25rem": "i", + "-2.5rem": "j", + "-2.75rem": "k", + "-2rem": "l", + "-3.5rem": "m", + "-3rem": "n", + "-4rem": "o", + "-5rem": "p", + "-6rem": "q", + "0.125rem": "r", + "0.25rem": "s", + "0.42857142857142855em": "t", + "0.47058823529411764em": "u", + "0.5rem": "v", + "0.7142857142857143em": "w", + "0.75rem": "x", + "0rem": "y", + "1.25rem": "z", + "1.5rem": "A", + "1.75rem": "B", + "1rem": "C", + "2.25rem": "D", + "2.5rem": "E", + "2.75rem": "F", + "2rem": "G", + "3.5rem": "H", + "3rem": "I", + "4rem": "J", + "5rem": "K", + "6rem": "L", + "auto": "M", + "calc(var(--F, var(--M)) * 3 / 8)": "N", + "calc(var(--F, var(--M)) / 2)": "O" + }, + "marginTop": { + "-0.125rem": "a", + "-0.25rem": "b", + "-0.5rem": "c", + "-0.75rem": "d", + "-1.25rem": "e", + "-1.5rem": "f", + "-1.75rem": "g", + "-1rem": "h", + "-2.25rem": "i", + "-2.5rem": "j", + "-2.75rem": "k", + "-2rem": "l", + "-3.5rem": "m", + "-3rem": "n", + "-4rem": "o", + "-5rem": "p", + "-6rem": "q", + "0.125rem": "r", + "0.25rem": "s", + "0.42857142857142855em": "t", + "0.47058823529411764em": "u", + "0.5rem": "v", + "0.7142857142857143em": "w", + "0.75rem": "x", + "0rem": "y", + "1.25rem": "z", + "1.5rem": "A", + "1.75rem": "B", + "1rem": "C", + "2.25rem": "D", + "2.5rem": "E", + "2.75rem": "F", + "2rem": "G", + "3.5rem": "H", + "3rem": "I", + "4rem": "J", + "5rem": "K", + "6rem": "L", + "auto": "M", + "calc(var(--F, var(--M)) * 3 / 8)": "N", + "calc(var(--F, var(--M)) / 2)": "O" + }, + "maxHeight": { + "100%": "a", + "100vh": "b", + "90vh": "c", + "auto": "d", + "calc(10rem * var(--s2-scale))": "e", + "fit-content": "f", + "max-content": "g", + "min-content": "h", + "none": "i" + }, + "maxWidth": { + "100%": "a", + "100vw": "b", + "90vw": "c", + "auto": "d", + "calc(10rem * var(--s2-scale))": "e", + "calc(12rem * var(--s2-scale))": "f", + "calc(13rem * var(--s2-scale))": "g", + "calc(20rem * var(--s2-scale))": "h", + "calc(23.75rem * var(--s2-scale))": "i", + "calc(33rem * var(--s2-scale))": "j", + "calc(48rem * var(--s2-scale))": "k", + "calc(60rem * var(--s2-scale))": "l", + "fit-content": "m", + "max-content": "n", + "min-content": "o", + "none": "p" + }, + "minHeight": { + "0px": "a", + "100%": "b", + "100vh": "c", + "auto": "d", + "calc(1.125rem * var(--s2-scale))": "e", + "calc(1.25rem * var(--s2-scale))": "f", + "calc(1.5rem * var(--s2-scale))": "g", + "calc(2.5rem * var(--s2-scale))": "h", + "calc(2rem * var(--s2-scale))": "i", + "calc(3.5rem * var(--s2-scale))": "j", + "calc(3rem * var(--s2-scale))": "k", + "calc(4rem * var(--s2-scale))": "l", + "fit-content": "m", + "max-content": "n", + "min-content": "o" + }, + "minWidth": { + "0px": "a", + "100%": "b", + "100vw": "c", + "auto": "d", + "calc(1.25rem * var(--s2-scale))": "e", + "calc(1.5rem * var(--s2-scale))": "f", + "calc(12.5rem * var(--s2-scale))": "g", + "calc(12rem * var(--s2-scale))": "h", + "calc(13.625rem * var(--s2-scale))": "i", + "calc(2.5rem * var(--s2-scale))": "j", + "calc(2rem * var(--s2-scale))": "k", + "calc(3rem * var(--s2-scale))": "l", + "calc(4rem * var(--s2-scale))": "m", + "calc(8rem * var(--s2-scale))": "n", + "fit-content": "o", + "max-content": "p", + "min-content": "q" + }, + "outlineColor": { + "ButtonBorder": "a", + "ButtonText": "b", + "Highlight": "c", + "black": "d", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 1)": "e", + "light-dark(rgb(225, 225, 225), rgb(50, 50, 50))": "f", + "light-dark(rgb(255, 255, 255), rgb(17, 17, 17))": "g", + "light-dark(rgb(75, 117, 255), rgb(64, 105, 253))": "h", + "rgb(from light-dark(rgb(0, 0, 0), rgb(255, 255, 255)) r g b / 10%)": "i", + "transparent": "j", + "white": "k" + }, + "overflowY": { + "auto": "a", + "clip": "b", + "hidden": "c", + "scroll": "d", + "visible": "e" + }, + "paddingBottom": { + "0.42857142857142855em": "a", + "0.47058823529411764em": "b", + "0.7142857142857143em": "c", + "0px": "d", + "12px": "e", + "16px": "f", + "20px": "g", + "24px": "h", + "28px": "i", + "2px": "j", + "32px": "k", + "36px": "l", + "40px": "m", + "44px": "n", + "48px": "o", + "4px": "p", + "56px": "q", + "64px": "r", + "80px": "s", + "8px": "t", + "96px": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "paddingInlineEnd": { + "0.42857142857142855em": "a", + "0.47058823529411764em": "b", + "0.7142857142857143em": "c", + "0px": "d", + "12px": "e", + "16px": "f", + "20px": "g", + "24px": "h", + "28px": "i", + "2px": "j", + "32px": "k", + "36px": "l", + "40px": "m", + "44px": "n", + "48px": "o", + "4px": "p", + "56px": "q", + "64px": "r", + "80px": "s", + "8px": "t", + "96px": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "paddingInlineStart": { + "0.42857142857142855em": "a", + "0.47058823529411764em": "b", + "0.7142857142857143em": "c", + "0px": "d", + "12px": "e", + "16px": "f", + "20px": "g", + "24px": "h", + "28px": "i", + "2px": "j", + "32px": "k", + "36px": "l", + "40px": "m", + "44px": "n", + "48px": "o", + "4px": "p", + "56px": "q", + "64px": "r", + "80px": "s", + "8px": "t", + "96px": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "paddingTop": { + "0.42857142857142855em": "a", + "0.47058823529411764em": "b", + "0.7142857142857143em": "c", + "0px": "d", + "12px": "e", + "16px": "f", + "20px": "g", + "24px": "h", + "28px": "i", + "2px": "j", + "32px": "k", + "36px": "l", + "40px": "m", + "44px": "n", + "48px": "o", + "4px": "p", + "56px": "q", + "64px": "r", + "80px": "s", + "8px": "t", + "96px": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "rowGap": { + "0.125rem": "a", + "0.25rem": "b", + "0.42857142857142855em": "c", + "0.47058823529411764em": "d", + "0.5rem": "e", + "0.7142857142857143em": "f", + "0.75rem": "g", + "0rem": "h", + "1.25rem": "i", + "1.5rem": "j", + "1.75rem": "k", + "1rem": "l", + "2.25rem": "m", + "2.5rem": "n", + "2.75rem": "o", + "2rem": "p", + "3.5rem": "q", + "3rem": "r", + "4rem": "s", + "5rem": "t", + "6rem": "u", + "calc(var(--F, var(--M)) * 3 / 8)": "v", + "calc(var(--F, var(--M)) / 2)": "w" + }, + "stroke": { + "Background": "a", + "ButtonBorder": "b", + "Highlight": "c", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.17)": "d", + "lch(from var(--s2-container-bg) calc((49.44 - l) * infinity) 0 0 / 0.94)": "e", + "light-dark(rgb(218, 218, 218), rgb(57, 57, 57))": "f", + "light-dark(rgb(225, 225, 225), rgb(50, 50, 50))": "g", + "light-dark(rgb(59, 99, 251), rgb(86, 129, 255))": "h", + "rgba(0, 0, 0, 0.12)": "i" + }, + "top": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "50%": "F", + "56px": "G", + "64px": "H", + "80px": "I", + "8px": "J", + "96px": "K", + "auto": "L" + }, + "transitionDuration": { + "130ms": "a", + "150ms": "b", + "200ms": "c", + "250ms": "d", + "500ms": "e" + }, + "transitionProperty": { + "all": "a", + "box-shadow": "b", + "color, background-color, var(--gp, color), border-color, text-decoration-color, fill, stroke": "c", + "color, background-color, var(--gp, color), border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, translate, scale, rotate, filter, backdrop-filter": "d", + "none": "e", + "opacity": "f", + "transform, translate, scale, rotate": "g" + }, + "width": { + "100%": "a", + "100vw": "b", + "50%": "c", + "auto": "d", + "calc(0.0625rem * var(--s2-scale))": "e", + "calc(0.125rem * var(--s2-scale))": "f", + "calc(0.375rem * var(--s2-scale))": "g", + "calc(0.5rem * var(--s2-scale))": "h", + "calc(0.625rem * var(--s2-scale))": "i", + "calc(0.75rem * var(--s2-scale))": "j", + "calc(0.875rem * var(--s2-scale))": "k", + "calc(1.125rem * var(--s2-scale))": "l", + "calc(1.25rem * var(--s2-scale))": "m", + "calc(1.375rem * var(--s2-scale))": "n", + "calc(1.5rem * var(--s2-scale))": "o", + "calc(1.625rem * var(--s2-scale))": "p", + "calc(10rem * var(--s2-scale))": "q", + "calc(12rem * var(--s2-scale))": "r", + "calc(13.625rem * var(--s2-scale))": "s", + "calc(15rem * var(--s2-scale))": "t", + "calc(1rem * var(--s2-scale))": "u", + "calc(2.25rem * var(--s2-scale))": "v", + "calc(2.5rem * var(--s2-scale))": "w", + "calc(2.75rem * var(--s2-scale))": "x", + "calc(20rem * var(--s2-scale))": "y", + "calc(21rem * var(--s2-scale))": "z", + "calc(25rem * var(--s2-scale))": "A", + "calc(26rem * var(--s2-scale))": "B", + "calc(2rem * var(--s2-scale))": "C", + "calc(3.5rem * var(--s2-scale))": "D", + "calc(36rem * var(--s2-scale))": "E", + "calc(3rem * var(--s2-scale))": "F", + "calc(4rem * var(--s2-scale))": "G", + "calc(6rem * var(--s2-scale))": "H", + "calc(7rem * var(--s2-scale))": "I", + "fit-content": "J", + "max-content": "K", + "min-content": "L" + }, + "--scaleX": { + "1": "b", + "-1": "a" + }, + "--scaleY": { + "1": "b", + "-1": "a" + }, + "alignContent": { + "baseline": "a", + "center": "b", + "end": "c", + "normal": "d", + "space-around": "e", + "space-between": "f", + "space-evenly": "g", + "start": "h", + "stretch": "i" + }, + "alignSelf": { + "auto": "a", + "baseline": "b", + "center": "c", + "end": "d", + "start": "e", + "stretch": "f" + }, + "animationDuration": { + "1000ms": "a", + "125ms": "b", + "150ms": "c" + }, + "animationFillMode": { + "backwards": "a", + "both": "b", + "forwards": "c", + "none": "d" + }, + "animationTimingFunction": { + "cubic-bezier(0, 0, 0.40, 1)": "a", + "cubic-bezier(0.45, 0, 0.4, 1)": "b", + "cubic-bezier(0.5, 0, 1, 1)": "c", + "linear": "d" + }, + "backgroundImage": { + "linear-gradient(to bottom right, var(--g0) 0%,var(--g1) 33%,var(--g2) 100%)": "a", + "linear-gradient(to bottom right, var(--g0) 0%,var(--g1) 66%,var(--g2) 100%)": "b", + "none": "c" + }, + "borderStyle": { + "dashed": "a", + "dotted": "b", + "double": "c", + "hidden": "d", + "none": "e", + "solid": "f" + }, + "borderTopWidth": { + "0px": "a", + "1px": "b", + "2px": "c", + "4px": "d" + }, + "bottom": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "56px": "F", + "64px": "G", + "80px": "H", + "8px": "I", + "96px": "J", + "auto": "K" + }, + "boxDecorationBreak": { + "clone": "a", + "slice": "b" + }, + "boxShadow": { + "0px 1px 6px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.36))": "a", + "0px 2px 8px light-dark(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.48))": "b", + "0px 6px 16px light-dark(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.6))": "c", + "none": "d" + }, + "boxSizing": { + "border-box": "a", + "content-box": "b" + }, + "colorScheme": { + "dark": "a", + "light": "b", + "light dark": "c" + }, + "contain": { + "content": "a", + "inline-size": "b", + "layout": "c", + "none": "d", + "paint": "e", + "size": "f", + "strict": "g", + "style": "h" + }, + "filter": { + "drop-shadow(0px 1px 6px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.36)))": "a", + "drop-shadow(0px 2px 8px light-dark(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.48)))": "b", + "drop-shadow0px 6px 16px light-dark(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.6))": "c", + "none": "d" + }, + "flexBasis": { + "0%": "a", + "0px": "b", + "100%": "c", + "auto": "d" + }, + "flexDirection": { + "column": "a", + "column-reverse": "b", + "row": "c", + "row-reverse": "d" + }, + "flexGrow": { + "0": "a", + "1": "b" + }, + "flexShrink": { + "0": "a", + "1": "b" + }, + "flexWrap": { + "nowrap": "a", + "wrap": "b", + "wrap-reverse": "c" + }, + "fontSynthesisWeight": { + "none": "a" + }, + "forcedColorAdjust": { + "auto": "a", + "none": "b" + }, + "insetInlineEnd": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "56px": "F", + "64px": "G", + "80px": "H", + "8px": "I", + "96px": "J", + "auto": "K" + }, + "insetInlineStart": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "56px": "F", + "64px": "G", + "80px": "H", + "8px": "I", + "96px": "J", + "auto": "K" + }, + "isolation": { + "auto": "a", + "isolate": "b" + }, + "justifyContent": { + "center": "a", + "end": "b", + "normal": "c", + "space-around": "d", + "space-between": "e", + "space-evenly": "f", + "start": "g", + "stretch": "h" + }, + "justifyItems": { + "center": "a", + "end": "b", + "start": "c", + "stretch": "d" + }, + "left": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "50%": "F", + "56px": "G", + "64px": "H", + "80px": "I", + "8px": "J", + "96px": "K", + "auto": "L" + }, + "lineHeight": { + "1.3": "a", + "1.5": "b", + "1.7": "c", + "round(1em * (1.15 + (1 - ((min(32, var(--s2-font-size-base, 14) * var(--fs)) - 10)) / 22) * 0.15), 2px)": "d" + }, + "listStyleType": { + "decimal": "a", + "disc": "b", + "none": "c" + }, + "objectFit": { + "contain": "a", + "cover": "b", + "fill": "c", + "none": "d", + "scale-down": "e" + }, + "opacity": { + "0": "a", + "1": "b" + }, + "order": { + "0": "a", + "1": "b" + }, + "outlineOffset": { + "-1px": "a", + "-2px": "b", + "-4px": "c", + "2px": "d" + }, + "outlineStyle": { + "dashed": "a", + "dotted": "b", + "double": "c", + "inset": "d", + "none": "e", + "solid": "f" + }, + "outlineWidth": { + "0px": "a", + "1px": "b", + "2px": "c", + "4px": "d" + }, + "overflowX": { + "auto": "a", + "clip": "b", + "hidden": "c", + "scroll": "d", + "visible": "e" + }, + "pointerEvents": { + "auto": "a", + "none": "b" + }, + "position": { + "absolute": "a", + "fixed": "b", + "relative": "c", + "static": "d", + "sticky": "e" + }, + "resize": { + "both": "a", + "horizontal": "b", + "none": "c", + "vertical": "d" + }, + "right": { + "-12px": "a", + "-16px": "b", + "-20px": "c", + "-24px": "d", + "-28px": "e", + "-2px": "f", + "-32px": "g", + "-36px": "h", + "-40px": "i", + "-44px": "j", + "-48px": "k", + "-4px": "l", + "-56px": "m", + "-64px": "n", + "-80px": "o", + "-8px": "p", + "-96px": "q", + "0px": "r", + "100%": "s", + "12px": "t", + "16px": "u", + "20px": "v", + "24px": "w", + "28px": "x", + "2px": "y", + "32px": "z", + "36px": "A", + "40px": "B", + "44px": "C", + "48px": "D", + "4px": "E", + "56px": "F", + "64px": "G", + "80px": "H", + "8px": "I", + "96px": "J", + "auto": "K" + }, + "rotate": { + "-90deg": "a", + "0deg": "b", + "180deg": "c", + "90deg": "d" + }, + "scrollPaddingInlineStart": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollPaddingTop": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "strokeWidth": { + "0": "a", + "1": "b", + "2": "c" + }, + "textAlign": { + "center": "a", + "end": "b", + "justify": "c", + "start": "d" + }, + "textDecoration": { + "none": "a", + "underline 1px": "b" + }, + "textOverflow": { + "clip": "a", + "ellipsis": "b" + }, + "transform": { + "rotate(-90deg)": "a", + "rotate(90deg)": "b", + "translate(-50%, -50%)": "c", + "translateY(-50%) translateX(-50%)": "d" + }, + "transformOrigin": { + "bottom": "a", + "bottom left": "b", + "bottom right": "c", + "center": "d", + "left": "e", + "right": "f", + "top": "g", + "top right": "h" + }, + "transitionDelay": { + "0ms": "a", + "160ms": "b" + }, + "transitionTimingFunction": { + "cubic-bezier(0, 0, 0.40, 1)": "a", + "cubic-bezier(0.45, 0, 0.4, 1)": "b", + "cubic-bezier(0.5, 0, 1, 1)": "c", + "linear": "d" + }, + "unicodeBidi": { + "bidi-override": "a", + "embed": "b", + "isolate": "c", + "isolate-override": "d", + "normal": "e", + "plaintext": "f" + }, + "userSelect": { + "all": "a", + "auto": "b", + "none": "c", + "text": "d" + }, + "verticalAlign": { + "baseline": "a", + "bottom": "b", + "middle": "c", + "sub": "d", + "super": "e", + "text-bottom": "f", + "text-top": "g", + "top": "h" + }, + "visibility": { + "collapse": "a", + "hidden": "b", + "visible": "c" + }, + "whiteSpace": { + "break-spaces": "a", + "normal": "b", + "nowrap": "c", + "pre": "d", + "pre-line": "e", + "pre-wrap": "f" + }, + "willChange": { + "auto": "a", + "contents": "b", + "scroll-position": "c", + "transform": "d" + }, + "zIndex": { + "0": "b", + "1": "c", + "2": "d", + "-1": "a" + }, + "-webkit-box-orient": { + "vertical": "a" + }, + "-webkit-line-clamp": { + "3": "a" + }, + "-webkit-tap-highlight-color": { + "rgba(0,0,0,0)": "a" + }, + "animationDirection": { + "alternate": "a", + "alternate-reverse": "b", + "normal": "c", + "reverse": "d" + }, + "animationIterationCount": { + "infinite": "a" + }, + "appearance": { + "auto": "a", + "none": "b" + }, + "backgroundAttachment": { + "fixed": "a", + "local": "b", + "scroll": "c" + }, + "backgroundBlendMode": { + "color": "a", + "color-burn": "b", + "color-dodge": "c", + "darken": "d", + "difference": "e", + "exclusion": "f", + "hard-light": "g", + "hue": "h", + "lighten": "i", + "luminosity": "j", + "multiply": "k", + "normal": "l", + "overlay": "m", + "saturation": "n", + "screen": "o", + "soft-light": "p" + }, + "backgroundClip": { + "border-box": "a", + "content-box": "b", + "padding-box": "c", + "text": "d" + }, + "backgroundOrigin": { + "border-box": "a", + "content-box": "b", + "padding-box": "c" + }, + "backgroundPosition": { + "bottom": "a", + "center": "b", + "left": "c", + "left bottom": "d", + "left top": "e", + "right": "f", + "right bottom": "g", + "right top": "h", + "top": "i" + }, + "backgroundRepeat": { + "no-repeat": "a", + "repeat": "b", + "repeat-x": "c", + "repeat-y": "d", + "round": "e", + "space": "f" + }, + "backgroundSize": { + "auto": "a", + "contain": "b", + "cover": "c" + }, + "borderCollapse": { + "collapse": "a", + "separate": "b" + }, + "borderSpacing": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "breakAfter": { + "all": "a", + "auto": "b", + "avoid": "c", + "avoid-page": "d", + "column": "e", + "left": "f", + "page": "g", + "right": "h" + }, + "breakBefore": { + "all": "a", + "auto": "b", + "avoid": "c", + "avoid-page": "d", + "column": "e", + "left": "f", + "page": "g", + "right": "h" + }, + "breakInside": { + "auto": "a", + "avoid": "b", + "avoid-column": "c", + "avoid-page": "d" + }, + "captionSide": { + "bottom": "a", + "top": "b" + }, + "clear": { + "both": "a", + "inline-end": "b", + "inline-start": "c", + "left": "d", + "none": "e", + "right": "f" + }, + "containIntrinsicHeight": { + "100%": "a", + "100vh": "b", + "auto": "c", + "fit-content": "d", + "max-content": "e", + "min-content": "f" + }, + "containIntrinsicWidth": { + "100%": "a", + "100vw": "b", + "auto": "c", + "fit-content": "d", + "max-content": "e", + "min-content": "f" + }, + "float": { + "inline-end": "a", + "inline-start": "b", + "left": "c", + "none": "d", + "right": "e" + }, + "gridAutoFlow": { + "column": "a", + "column dense": "b", + "dense": "c", + "row": "d", + "row dense": "e" + }, + "gridAutoRows": { + "min-content": "a" + }, + "hyphens": { + "auto": "a", + "manual": "b", + "none": "c" + }, + "justifySelf": { + "auto": "a", + "center": "b", + "end": "c", + "start": "d", + "stretch": "e" + }, + "listStylePosition": { + "inside": "a", + "outside": "b" + }, + "mixBlendMode": { + "color": "a", + "color-burn": "b", + "color-dodge": "c", + "darken": "d", + "difference": "e", + "exclusion": "f", + "hard-light": "g", + "hue": "h", + "lighten": "i", + "luminosity": "j", + "multiply": "k", + "normal": "l", + "overlay": "m", + "plus-darker": "n", + "plus-lighter": "o", + "saturation": "p", + "screen": "q", + "soft-light": "r" + }, + "objectPosition": { + "bottom": "a", + "center": "b", + "left": "c", + "left bottom": "d", + "left top": "e", + "right": "f", + "right bottom": "g", + "right top": "h", + "top": "i" + }, + "overflow": { + "hidden": "a" + }, + "overscrollBehaviorX": { + "auto": "a", + "contain": "b", + "none": "c" + }, + "overscrollBehaviorY": { + "auto": "a", + "contain": "b", + "none": "c" + }, + "scale": { + "var(--scaleX, 1) var(--scaleY, 1)": "a" + }, + "scrollBehavior": { + "auto": "a", + "smooth": "b" + }, + "scrollMarginBottom": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollMarginInlineEnd": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollMarginInlineStart": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollMarginTop": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollPaddingBottom": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollPaddingInlineEnd": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "scrollSnapAlign": { + "center": "a", + "end": "b", + "none": "c", + "start": "d" + }, + "scrollSnapStop": { + "always": "a", + "normal": "b" + }, + "scrollSnapType": { + "both": "a", + "both mandatory": "b", + "x": "c", + "x mandatory": "d", + "y": "e", + "y mandatory": "f" + }, + "tableLayout": { + "auto": "a", + "fixed": "b" + }, + "textIndent": { + "0.125rem": "a", + "0.25rem": "b", + "0.5rem": "c", + "0.75rem": "d", + "0rem": "e", + "1.25rem": "f", + "1.5rem": "g", + "1.75rem": "h", + "1rem": "i", + "2.25rem": "j", + "2.5rem": "k", + "2.75rem": "l", + "2rem": "m", + "3.5rem": "n", + "3rem": "o", + "4rem": "p", + "5rem": "q", + "6rem": "r" + }, + "textTransform": { + "capitalize": "a", + "lowercase": "b", + "none": "c", + "uppercase": "d" + }, + "textUnderlineOffset": { + "1px": "a" + }, + "textWrap": { + "balance": "a", + "nowrap": "b", + "pretty": "c", + "wrap": "d" + }, + "touchAction": { + "auto": "a", + "manipulation": "b", + "none": "c", + "pan-x": "d", + "pan-y": "e", + "pinch-zoom": "f" + }, + "translate": { + "var(--translateX, 0) var(--translateY, 0)": "a" + }, + "wordBreak": { + "break-all": "a", + "keep-all": "b", + "normal": "c" + }, + "--fs": { + "pow(1.125, -2)": "a", + "pow(1.125, -1)": "b", + "pow(1.125, 0)": "c", + "pow(1.125, 1)": "d", + "pow(1.125, 2)": "e", + "pow(1.125, 3)": "f", + "pow(1.125, 4)": "g", + "pow(1.125, 6)": "h" + } + }, + "conditions": { + ":empty": "a", + ":first-child": "b", + ":lang(ar)": "c", + ":lang(he)": "d", + ":lang(ja)": "e", + ":lang(ja, ko, zh, zh-Hant, zh-Hans)": "f", + ":lang(ko)": "g", + ":lang(zh)": "h", + ":lang(zh-Hans, zh-CN, zh-SG)": "i", + ":lang(zh-hant)": "j", + ":last-child": "k", + "@media (forced-colors: active)": "l", + "@media (height < 25rem)": "m", + "@media (min-width: 40rem)": "n", + "@media (min-width: 48rem)": "o", + "@media (min-width: 64rem)": "p", + "@media (min-width: 80rem)": "q", + "@media (min-width: 96rem)": "r", + "@media not ((hover: hover) and (pointer: fine))": "s", + "@supports (contain-intrinsic-width: 1px)": "t" + } +} diff --git a/packages/@react-spectrum/s2-ai/src/style-macro.tsx b/packages/@react-spectrum/s2-ai/src/style-macro.tsx new file mode 100644 index 00000000000..cce10985a24 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/src/style-macro.tsx @@ -0,0 +1,1147 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import type { + Condition, + CSSProperties, + CSSValue, + CustomValue, + Property, + PropertyValueDefinition, + PropertyValueMap, + RenderProps, + ShorthandProperty, + StyleFunction, + StyleValue, + Theme, + ThemeProperties, + Value +} from './types'; +import fs from 'fs'; +import * as propertyInfo from './properties.json'; + +// Postfix all class names with version for now. +const json = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8')); +const POSTFIX = json.version.includes('nightly') + ? json.version.match(/-nightly-(.*)/)[1] + : json.version.replace(/[0.]/g, ''); + +export class ArbitraryProperty implements Property { + property: string; + toCSS: (value: T) => CSSValue; + + constructor(property: string, toCSS?: (value: T) => CSSValue) { + this.property = property; + this.toCSS = toCSS || (value => String(value)); + } + + get cssProperties(): string[] { + return [this.property]; + } + + toCSSValue(value: T): PropertyValueDefinition { + return this.toCSS(value); + } + + toCSSProperties( + customProperty: string | null, + value: PropertyValueDefinition + ): PropertyValueDefinition<[CSSProperties]> { + return mapConditionalValue(value, value => [ + {[customProperty || this.property]: String(value)} + ]); + } +} + +export class MappedProperty + extends ArbitraryProperty + implements Property +{ + mapping: PropertyValueMap | string[]; + + constructor(property: string, mapping: PropertyValueMap | string[]) { + super(property); + this.mapping = mapping; + } + + toCSSValue(value: T): PropertyValueDefinition { + if (Array.isArray(this.mapping)) { + if (!this.mapping.includes(String(value))) { + throw new Error('Invalid style value: ' + value); + } + return value; + } else { + let res = this.mapping[String(value)]; + if (res == null) { + throw new Error('Invalid style value: ' + value); + } + return res; + } + } +} + +export type Color = C | `${string}/${number}`; +export class ColorProperty + extends MappedProperty + implements Property> +{ + toCSSValue(value: Color): PropertyValueDefinition { + let [color, opacity] = value.split('/'); + return mapConditionalValue(this.mapping[color], value => { + return opacity ? `rgb(from ${value} r g b / ${opacity}%)` : value; + }); + } +} + +export type LengthPercentageUnit = + | '%' + | 'vw' + | 'svw' + | 'dvw' + | 'vh' + | 'svh' + | 'dvh' + | 'vmin' + | 'svmin' + | 'dvmin' + | 'vmax' + | 'svmax' + | 'dvmax' + | 'cqw' + | 'cqh' + | 'cqmin' + | 'cqmax'; +export type LengthPercentage = `${number}${LengthPercentageUnit}`; + +export class PercentageProperty + extends MappedProperty + implements Property +{ + constructor(property: string, mapping: PropertyValueMap | string[]) { + super(property, mapping); + } + + toCSSValue(value: T | LengthPercentage): PropertyValueDefinition { + if ( + typeof value === 'string' && + /^-?\d+(?:\.\d+)?(%|vw|svw|dvw|vh|svh|dvh|vmin|svmin|dvmin|vmax|svmax|dvmax|cqw|cqh|cqmin|cqmax)$/.test( + value + ) + ) { + return value; + } + + return super.toCSSValue(value as T); + } +} + +export class SizingProperty + extends PercentageProperty + implements Property +{ + numberToCSS: (value: number) => string; + + constructor( + property: string, + mapping: PropertyValueMap | string[], + numberToCSS: (value: number) => string + ) { + super(property, mapping); + this.numberToCSS = numberToCSS; + } + + toCSSValue(value: T | LengthPercentage | number): PropertyValueDefinition { + if (typeof value === 'number') { + return value === 0 ? '0px' : this.numberToCSS(value); + } + + return super.toCSSValue(value); + } +} + +export class ExpandedProperty implements Property { + cssProperties: string[]; + mapping: Property | null; + expand: (v: T | CSSValue) => CSSProperties; + + constructor( + properties: string[], + expand: (v: T | CSSValue) => CSSProperties, + mapping?: Property | PropertyValueMap + ) { + this.cssProperties = properties; + this.expand = expand; + if (mapping instanceof MappedProperty) { + this.mapping = mapping; + } else if (mapping) { + this.mapping = new MappedProperty(properties[0], mapping as any); + } else { + this.mapping = null; + } + } + + toCSSValue(value: T): PropertyValueDefinition { + if (!this.mapping) { + return value; + } + + return this.mapping.toCSSValue(value); + } + + toCSSProperties( + customProperty: string | null, + value: PropertyValueDefinition + ): PropertyValueDefinition<[CSSProperties]> { + if (customProperty) { + throw new Error( + 'Style properties that expand into multiple CSS properties cannot be set as CSS variables.' + ); + } + return mapConditionalValue(value, value => [this.expand(value)]); + } +} + +function mapConditionalValue( + value: PropertyValueDefinition, + fn: (value: T) => U +): PropertyValueDefinition { + if (typeof value === 'object' && !Array.isArray(value)) { + let res: PropertyValueDefinition = {}; + for (let condition in value) { + res[condition] = mapConditionalValue((value as any)[condition], fn); + } + return res; + } else { + return fn(value); + } +} + +function mapConditionalShorthand>( + value: PropertyValueDefinition, + fn: ShorthandProperty +): {[property: string]: StyleValue} { + if (typeof value === 'object') { + let res = {}; + for (let condition in value) { + let properties = mapConditionalShorthand(value[condition], fn); + for (let property in properties) { + res[property] ??= {}; + res[property][condition] = properties[property]; + } + } + return res; + } else { + return fn(value); + } +} + +export function parseArbitraryValue(value: Value): string | undefined { + if (typeof value === 'string' && value.startsWith('--')) { + return `var(${value})`; + } else if (typeof value === 'string' && value[0] === '[' && value[value.length - 1] === ']') { + return value.slice(1, -1); + } else if ( + typeof value === 'string' && + (/^(var|calc|min|max|clamp|round|mod|rem|sin|cos|tan|asin|acos|atan|atan2|pow|sqrt|hypot|log|exp|abs|sign)\(.+\)$/.test( + value + ) || + /^(inherit|initial|unset)$/.test(value)) + ) { + return value; + } +} + +function shortCSSPropertyName(property: string) { + return propertyInfo.properties[property] ?? generateArbitraryValueSelector(property, true); +} + +function classNamePrefix(property: string, cssProperty: string) { + let className = propertyInfo.properties[cssProperty]; + if (className && property === '--' + className) { + return '-' + className + '_-'; + } + + if (className && !property.startsWith('--')) { + return className; + } + + return '-' + generateArbitraryValueSelector(property, true) + '-'; +} + +interface MacroContext { + addAsset(asset: {type: string; content: string}): void; +} + +let isCompilingDependencies: boolean | null | string = false; + +export function createTheme( + theme: T +): StyleFunction, 'default' | Extract> { + let properties = new Map>( + Object.entries(theme.properties).map(([k, v]) => { + if (!Array.isArray(v) && v.cssProperties) { + return [k, v as Property]; + } + + return [k, new MappedProperty(k, v as any)]; + }) + ); + + let dependencies = new Set(); + let hasConditions = false; + return function style(this: MacroContext | void, style, allowedOverrides?: readonly string[]) { + // Check if `this` is undefined, which means style was not called as a macro but as a normal function. + // We also check if this is globalThis, which happens in non-strict mode bundles. + // Also allow style to be called as a normal function in tests. + // @ts-ignore + + if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') { + throw new Error('The style macro must be imported with {type: "macro"}.'); + } + + // Generate rules for each property. + let rules = new Map(); + let values = new Map(); + dependencies.clear(); + let usedPriorities = 0; + let setRules = (key: string, value: [number, Rule[]]) => { + usedPriorities = Math.max(usedPriorities, value[0]); + rules.set(key, new GroupRule(value[1])); + }; + + hasConditions = false; + for (let key in style) { + let value = style[key]!; + let themeProperty = key; + values.set(key, value); + + // Get the type of custom properties in the theme. + if (key.startsWith('--')) { + themeProperty = value.type; + value = value.value; + } + + // Expand shorthands to longhands so that merging works as expected. + if (theme.shorthands[key]) { + let shorthand = theme.shorthands[key]; + if (typeof shorthand === 'function') { + let expanded = mapConditionalShorthand(value, shorthand); + for (let k in expanded) { + let v = expanded[k]; + values.set(k, v); + setRules(k, compileValue(k, k, v)); + } + } else { + for (let prop of shorthand) { + values.set(prop, value); + setRules(prop, compileValue(prop, prop, value)); + } + } + } else if (themeProperty in theme.properties) { + setRules(key, compileValue(key, themeProperty, value)); + } + } + + // For properties referenced by self(), rewrite the declarations to assign + // to an intermediary custom property so we can access the value. + for (let dep of dependencies) { + let value = values.get(dep); + if (value != null) { + if (!(dep in theme.properties)) { + throw new Error(`Unknown dependency ${dep}`); + } + let prop = properties.get(dep)!; + let name = `--${shortCSSPropertyName(prop.cssProperties[0])}`; + // Could potentially use @property to prevent the var from inheriting in children. + isCompilingDependencies = dep; + setRules(name, compileValue(name, dep, value)); + isCompilingDependencies = null; + setRules(dep, compileValue(dep, dep, name)); + isCompilingDependencies = false; + } + } + dependencies.clear(); + + let css = ''; + + // Declare layers for each priority ahead of time so the order is always correct. + css += '@layer '; + let first = true; + for (let i = 0; i <= usedPriorities; i++) { + if (first) { + first = false; + } else { + css += ', '; + } + css += layerName(generateName(i, true)); + } + css += ';\n\n'; + + // If allowed overrides are provided, generate code to match the input override string and include only allowed classes. + // Also generate a variable for each overridable property that overlaps with the style definition. If those are defined, + // the defaults from the style definition are omitted. + let allowedOverridesSet = new Set(); + let js = + process.env.NODE_ENV !== 'production' + ? 'let rules = " ", currentRules = {};\n' + : 'let rules = " ";\n'; + if (allowedOverrides?.length) { + for (let property of allowedOverrides) { + let shorthand = theme.shorthands[property]; + let props = Array.isArray(shorthand) ? shorthand : [property]; + for (let property of props) { + if (property.startsWith('--')) { + allowedOverridesSet.add(property); + continue; + } + + let prop = properties.get(property); + if (!prop) { + throw new Error(`Invalid property ${property} in allowedOverrides`); + } + for (let property of prop.cssProperties) { + allowedOverridesSet.add(property); + } + } + } + + let loop = ''; + for (let property of rules.keys()) { + let prop = properties.get(property); + if (prop) { + for (let property of prop.cssProperties) { + // oxlint-disable-next-line max-depth + if (property && allowedOverridesSet.has(property)) { + let selector = classNamePrefix(property, property); + let p = property.replace('--', '__'); + js += `let ${p} = false;\n`; + loop += ` if (p[1] === ${JSON.stringify(selector)}) ${p} = true;\n`; + } + } + } else if (property.startsWith('--') && allowedOverridesSet.has(property)) { + let selector = classNamePrefix(property, property); + let p = property.replace('--', '__'); + js += `let ${p} = false;\n`; + loop += ` if (p[1] === ${JSON.stringify(selector)}) ${p} = true;\n`; + } + } + + let macroPart = process.env.NODE_ENV !== 'production' ? '|-macro\\$' : ''; + let regex = `/(?:^|\\s)(${[...allowedOverridesSet].map(p => classNamePrefix(p, p)).join('|')}${macroPart})[^\\s]+/g`; + if (loop) { + js += `let matches = String(overrides || '').matchAll(${regex});\n`; + js += 'for (let p of matches) {\n'; + js += loop; + js += ' rules += p[0];\n'; + js += '}\n'; + } else { + js += `rules += (String(overrides || '').match(${regex}) || []).join('')\n`; + } + } + + // Generate JS and CSS for each rule. + let isStatic = !(hasConditions || allowedOverrides); + let className = ''; + let rulesByLayer = new Map(); + let rootRule = new GroupRule([...rules.values()]); + if (isStatic) { + className += rootRule.getStaticClassName(); + } else { + js += rootRule.toJS(allowedOverridesSet) + '\n'; + } + rootRule.toCSS(rulesByLayer); + + for (let [layer, rules] of rulesByLayer) { + css += `@layer ${layerName(layer)} {\n`; + css += rules.join('\n\n'); + css += '}\n\n'; + } + // @ts-expect-error + let loc = this?.loc?.filePath + ':' + this?.loc?.line + ':' + this?.loc?.col; + if (isStatic && process.env.NODE_ENV !== 'production') { + let id = toBase62(hash(className + loc)); + css += `.-macro-static-${id} { + --macro-data-${id}: ${JSON.stringify({style, loc})}; + }\n\n`; + className += ` -macro-static-${id}`; + } + + if (this && typeof this.addAsset === 'function') { + this.addAsset({ + type: 'css', + content: css + }); + } + + if (isStatic) { + return className; + } + + if (process.env.NODE_ENV !== 'production') { + js += `let targetRules = rules + ${JSON.stringify(loc)};\n`; + js += + 'let hash = 5381;for (let i = 0; i < targetRules.length; i++) { hash = ((hash << 5) + hash) + targetRules.charCodeAt(i) >>> 0; }\n'; + js += 'let hashStr = hash.toString(36);\n'; + js += 'rules += " -macro-dynamic-" + hashStr;\n'; + // Skip global __styleMacroDynamic__ in Jest so we dont' pollute the test environment and don't cause issues with timer advancement. + if (!process.env.JEST_WORKER_ID) { + js += 'if (typeof window !== "undefined") {\n'; + js += ' let g = window.__styleMacroDynamic__;\n'; + js += ' if (!g) {\n'; + js += ' g = window.__styleMacroDynamic__ = { map: {}, _timer: null };\n'; + js += ' g._timer = setInterval(function() {\n'; + js += ' for (let k in g.map) {\n'; + js += + ' try { if (!document.querySelector("." + CSS.escape(k))) delete g.map[k]; } catch (e) {}\n'; + js += ' }\n'; + js += ' }, 300000);\n'; + js += ' }\n'; + js += ` g.map["-macro-dynamic-" + hashStr] = { style: currentRules, loc: ${JSON.stringify(loc)} };\n`; + js += '}\n'; + } + } + js += 'return rules;'; + if (allowedOverrides) { + return new Function('props', 'overrides', js) as any; + } + return new Function('props', js) as any; + }; + + function compileValue( + property: string, + themeProperty: string, + value: StyleValue, any> + ) { + return conditionalToRules( + value as any, + 0, + new Set(), + new Set(), + (value, priority, conditions, skipConditions) => { + return compileRule(property, themeProperty, value, priority, conditions, skipConditions); + } + ); + } + + function conditionalToRules

    ( + value: PropertyValueDefinition

    , + parentPriority: number, + currentConditions: Set, + skipConditions: Set, + fn: ( + value: P, + priority: number, + conditions: Set, + skipConditions: Set + ) => [number, Rule[]] + ): [number, Rule[]] { + if (value && typeof value === 'object' && !Array.isArray(value)) { + let rules: Rule[] = []; + + // Later conditions in parent rules override conditions in child rules. + let subSkipConditions = new Set([...skipConditions, ...Object.keys(value)]); + + // Skip the default condition if we're already filtering by one of the other possible conditions. + // For example, if someone specifies `dark: 'gray-400'`, only include the dark version of `gray-400` from the theme. + let skipDefault = Object.keys(value).some(k => currentConditions.has(k)); + let wasCSSCondition = false; + let priority = parentPriority; + + for (let condition in value) { + if (skipConditions.has(condition) || (condition === 'default' && skipDefault)) { + continue; + } + subSkipConditions.delete(condition); + + let val = value[condition]; + + // If a theme condition comes after runtime conditions, create a new grouping. + // This makes the CSS class unconditional so it appears outside the `else` block in the JS. + // The @layer order in the generated CSS will ensure that it overrides classes applied by runtime conditions. + let isCSSCondition = condition in theme.conditions || /^[@:]/.test(condition); + if (!wasCSSCondition && isCSSCondition && rules.length) { + rules = [new GroupRule(rules)]; + } + wasCSSCondition = isCSSCondition; + + // Increment the current priority whenever we see a new CSS condition. + if (isCSSCondition) { + priority++; + } + + // If this is a runtime condition, inherit the priority from the parent rule. + // Otherwise, use the current maximum of the parent and current priorities. + let rulePriority = isCSSCondition ? priority : parentPriority; + + if ( + condition === 'default' || + isCSSCondition || + /^is[A-Z]/.test(condition) || + /^allows[A-Z]/.test(condition) + ) { + let subConditions = currentConditions; + if (isCSSCondition) { + subConditions = new Set([...currentConditions, condition]); + } + let [subPriority, subRules] = conditionalToRules( + val, + rulePriority, + subConditions, + subSkipConditions, + fn + ); + rules.push(...compileCondition(currentConditions, condition, priority, subRules)); + priority = Math.max(priority, subPriority); + } else if (val && typeof val === 'object' && !Array.isArray(val)) { + for (let key in val) { + let branchValue = val[key]; + // If this branch has no default, inherit the parent's default so e.g. forcedColors.default + // applies when selectionStyle.highlight doesn't define its own default. + // eslint-disable-next-line max-depth + if ( + value.default !== undefined && + branchValue && + typeof branchValue === 'object' && + !Array.isArray(branchValue) && + !('default' in branchValue) + ) { + branchValue = {default: value.default, ...branchValue}; + } + let [subPriority, subRules] = conditionalToRules( + branchValue, + rulePriority, + currentConditions, + subSkipConditions, + fn + ); + rules.push( + ...compileCondition( + currentConditions, + `${condition} === ${JSON.stringify(key)}`, + priority, + subRules + ) + ); + priority = Math.max(priority, subPriority); + } + } + } + return [priority, rules]; + } else { + // @ts-ignore - broken in non-strict? + return fn(value, parentPriority, currentConditions, skipConditions); + } + } + + function compileCondition( + conditions: Set, + condition: string, + priority: number, + rules: Rule[] + ): Rule[] { + if (condition === 'default' || conditions.has(condition)) { + return [new GroupRule(rules)]; + } + + if (condition in theme.conditions || /^[@:]/.test(condition)) { + // Conditions starting with : are CSS pseudo classes. Nest them inside the parent rule. + let prelude = theme.conditions[condition] || condition; + let preludes = Array.isArray(prelude) ? prelude : [prelude]; + return preludes.map(prelude => { + if (prelude.startsWith(':')) { + let rulesWithPseudo = rules.map(rule => { + rule = rule.copy(); + rule.addPseudo(prelude); + return rule; + }); + + return new GroupRule(rulesWithPseudo, generateName(priority, true)); + } + + // Otherwise, wrap the rule in the condition (e.g. @media). + // Top level layer is based on the priority of the rule, not the condition. + // Also group in a sub-layer based on the condition so that lightningcss can more effectively deduplicate rules. + let layer = `${generateName(priority, true)}.${propertyInfo.conditions[prelude] || generateArbitraryValueSelector(condition, true)}`; + return new AtRule(rules, prelude, layer, condition); + }); + } + + hasConditions = true; + return [new ConditionalRule(rules, condition)]; + } + + function compileRule( + property: string, + themeProperty: string, + value: Value, + priority: number, + conditions: Set, + skipConditions: Set + ): [number, Rule[]] { + let propertyFunction = properties.get(themeProperty); + if (propertyFunction) { + // Expand value to conditional CSS values, and then to rules. + let propertyValue = value; + let arbitrary = parseArbitraryValue(value); + let cssValue = arbitrary ? arbitrary : propertyFunction.toCSSValue(value); + let cssProperties = propertyFunction.toCSSProperties( + property.startsWith('--') ? property : null, + cssValue + ); + + return conditionalToRules( + cssProperties, + priority, + conditions, + skipConditions, + (value, priority, conditions) => { + let [obj] = value; + let rules: Rule[] = []; + for (let key in obj) { + let k = key as any; + let value = obj[k]; + if (value === undefined) { + continue; + } + if (typeof value === 'string') { + // Replace self() references with variables and track the dependencies. + value = value.replace(/self\(([a-zA-Z]+)/g, (_, v) => { + let prop = properties.get(v); + if (!prop) { + throw new Error(`self(${v}) is invalid. ${v} is not a known property.`); + } + let cssProperties = prop.cssProperties; + if (cssProperties.length !== 1) { + throw new Error( + `self(${v}) is not supported. ${v} expands to multiple CSS properties.` + ); + } + dependencies.add(v); + return `var(--${shortCSSPropertyName(cssProperties[0])}`; + }); + } + + // Generate selector. This consists of three parts: property, conditions, value. + let cssProperty = key; + if (property.startsWith('--')) { + cssProperty = propertyFunction.cssProperties[0]; + } + + let className = classNamePrefix(key, cssProperty); + if (conditions.size > 0) { + for (let condition of conditions) { + let prelude = theme.conditions[condition] || condition; + let preludes = Array.isArray(prelude) ? prelude : [prelude]; + for (let prelude of preludes) { + className += + propertyInfo.conditions[prelude] || generateArbitraryValueSelector(condition); + } + } + } + + if (cssProperty !== key) { + className += shortCSSPropertyName(cssProperty); + } + + className += + propertyInfo.values[cssProperty]?.[String(value)] ?? + generateArbitraryValueSelector(String(value)); + className += POSTFIX; + rules.push( + new StyleRule( + className, + key, + String(value), + isCompilingDependencies ? themeProperty : property, + propertyValue + ) + ); + } + + return [0, rules]; + } + ); + } else { + throw new Error('Unknown property ' + themeProperty); + } + } +} + +function kebab(property: string) { + if (property.startsWith('--')) { + return property; + } + return property.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`); +} + +// Generate a class name from a number, e.g. index within the theme. +// This maps to an alphabet containing lower case letters, upper case letters, and numbers. +// For numbers larger than 62, an underscore is prepended. +// This encoding allows easy parsing to enable runtime merging by property. +function generateName(index: number, atStart = false): string { + if (index < 26) { + // lower case letters + return String.fromCharCode(index + 97); + } + + if (index < 52) { + // upper case letters + return String.fromCharCode(index - 26 + 65); + } + + if (index < 62 && !atStart) { + // numbers + return String.fromCharCode(index - 52 + 48); + } + + return '_' + generateName(index - (atStart ? 52 : 62)); +} + +// For arbitrary values, we use a hash of the string to generate the class name. +function generateArbitraryValueSelector(v: string, atStart = false) { + let c = toBase62(hash(v)); + if (atStart && /^[0-9]/.test(c)) { + c = `_${c}`; + } + return c; +} + +function toBase62(value: number) { + if (value === 0) { + return generateName(value); + } + + let res = ''; + while (value) { + let remainder = value % 62; + res += generateName(remainder); + value = Math.floor((value - remainder) / 62); + } + + return res; +} + +// djb2 hash function. +// http://www.cse.yorku.ca/~oz/hash.html +function hash(v: string) { + let hash = 5381; + for (let i = 0; i < v.length; i++) { + hash = ((hash << 5) + hash + v.charCodeAt(i)) >>> 0; + } + return hash; +} + +function layerName(name: string) { + // All of our layers should be sub-layers of a single parent layer, so that + // the unsafe overrides layer always comes after. + return `_.${name}`; +} + +interface Rule { + addPseudo(prelude: string): void; + getStaticClassName(): string; + toCSS(rulesByLayer: Map, preludes?: string[], layer?: string): void; + toJS(allowedOverridesSet: Set, indent?: string): string; + copy(): Rule; +} + +let conditionStack: string[] = []; + +/** A CSS style rule. */ +class StyleRule implements Rule { + className: string; + pseudos: string; + property: string; + value: string; + themeProperty: string | undefined; + themeValue: Value | undefined; + + constructor( + className: string, + property: string, + value: string, + themeProperty: string | undefined, + themeValue: Value | undefined + ) { + this.className = className; + this.pseudos = ''; + this.property = property; + this.value = value; + if (process.env.NODE_ENV !== 'production' && isCompilingDependencies !== null) { + this.themeProperty = themeProperty; + this.themeValue = themeValue; + } + } + + copy(): Rule { + let rule = new StyleRule( + this.className, + this.property, + this.value, + this.themeProperty, + this.themeValue + ); + rule.pseudos = this.pseudos; + return rule; + } + + addPseudo(prelude: string) { + this.pseudos += prelude; + } + + getStaticClassName(): string { + return ' ' + this.className; + } + + toCSS(rulesByLayer: Map, preludes: string[] = [], layer = 'a') { + let prelude = `.${this.className}${this.pseudos}`; + preludes.push(prelude); + + // Nest rule in our stack of preludes (e.g. media queries/selectors). + let content = ' '; + preludes.forEach((p, i) => { + content += `${p} {\n${' '.repeat((i + 2) * 2)}`; + }); + content += `${kebab(this.property)}: ${this.value};\n`; + preludes.map((_, i) => { + content += `${' '.repeat((preludes.length - i) * 2)}}\n`; + }); + + // Group rule into the appropriate layer. + let rules = rulesByLayer.get(layer); + if (!rules) { + rules = []; + rulesByLayer.set(layer, rules); + } + rules.push(content); + preludes.pop(); + } + + toJS(allowedOverridesSet: Set, indent = ''): string { + let res = ''; + if (allowedOverridesSet.has(this.property)) { + res += `${indent}if (!${this.property.replace('--', '__')}) `; + } + res += `${indent}rules += ' ${this.className}';`; + if (process.env.NODE_ENV !== 'production' && this.themeProperty) { + let name = this.themeProperty; + if (this.pseudos) { + conditionStack.push(this.pseudos); + } + let propertyName = JSON.stringify(name); + let valueJson = JSON.stringify(this.themeValue); + if (conditionStack.length) { + // name += ` (${conditionStack.join(', ')})`; + let conditionKey = JSON.stringify(conditionStack.join(' && ')); + // Ensure currentRules[name] is an object, converting from simple value if needed + res += ` currentRules[${propertyName}] = typeof currentRules[${propertyName}] === 'object' ? currentRules[${propertyName}] : {"default": currentRules[${propertyName}]};`; + // Set the value for this specific condition + res += ` currentRules[${propertyName}][${conditionKey}] = ${valueJson};`; + } else { + res += ` currentRules[${propertyName}] = ${valueJson};`; + } + if (this.pseudos) { + conditionStack.pop(); + } + } + return res; + } +} + +/** Base class for rules that contain other rules. */ +class GroupRule implements Rule { + rules: Rule[]; + layer: string | null; + + constructor(rules: Rule[], layer?: string | null) { + this.rules = rules; + this.layer = layer ?? null; + } + + copy(): Rule { + return new GroupRule( + this.rules.map(rule => rule.copy()), + this.layer + ); + } + + addPseudo(prelude: string) { + for (let rule of this.rules) { + rule.addPseudo(prelude); + } + } + + getStaticClassName(): string { + return this.rules.map(rule => rule.getStaticClassName()).join(''); + } + + toCSS(rulesByLayer: Map, preludes?: string[], layer?: string) { + for (let rule of this.rules) { + rule.toCSS(rulesByLayer, preludes, this.layer || layer); + } + } + + toJS(allowedOverridesSet: Set, indent = ''): string { + let rules = this.rules.slice(); + let conditional = rules + .filter(rule => rule instanceof ConditionalRule) + .reverse() + .map((rule, i) => { + return `${i > 0 ? ' else ' : ''}${rule.toJS(allowedOverridesSet, indent)}`; + }); + + let elseCases = rules + .filter(rule => !(rule instanceof ConditionalRule)) + .map(rule => rule.toJS(allowedOverridesSet, indent)); + if (conditional.length && elseCases.length) { + return `${conditional.join('')} else {\n${indent} ${elseCases.join('\n' + indent + ' ')}\n${indent}}`; + } + + if (conditional.length) { + return conditional.join(''); + } + + return elseCases.join('\n' + indent); + } +} + +/** A rule that applies conditionally in CSS (e.g. @media). */ +class AtRule extends GroupRule { + prelude: string; + themeCondition: string | null; + + constructor(rules: Rule[], prelude: string, layer: string | null, themeCondition: string | null) { + super(rules, layer); + this.prelude = prelude; + this.themeCondition = themeCondition; + } + + copy(): Rule { + return new AtRule( + this.rules.map(rule => rule.copy()), + this.prelude, + this.layer, + this.themeCondition + ); + } + + toCSS(rulesByLayer: Map, preludes: string[] = [], layer?: string): void { + preludes.push(this.prelude); + super.toCSS(rulesByLayer, preludes, layer); + preludes?.pop(); + } + + toJS(allowedOverridesSet: Set, indent?: string): string { + conditionStack.push(this.themeCondition || this.prelude); + let res = super.toJS(allowedOverridesSet, indent); + conditionStack.pop(); + return res; + } +} + +/** A rule that applies conditionally at runtime. */ +class ConditionalRule extends GroupRule { + condition: string; + + constructor(rules: Rule[], condition: string) { + super(rules); + this.condition = condition; + } + + copy(): Rule { + return new ConditionalRule( + this.rules.map(rule => rule.copy()), + this.condition + ); + } + + getStaticClassName(): string { + throw new Error('Conditional rules cannot be compiled to a static class name. This is a bug.'); + } + + toJS(allowedOverridesSet: Set, indent = ''): string { + conditionStack.push(this.condition); + let res = `${indent}if (props.${this.condition}) {\n${super.toJS(allowedOverridesSet, indent + ' ')}\n${indent}}`; + conditionStack.pop(); + return res; + } +} + +/** + * Injects a raw CSS string into the style system. The CSS is wrapped in a generated + * class name and placed within the specified `@layer`. Returns the generated class name. + * This is an escape hatch for advanced cases (e.g. pseudo selectors or features not yet + * available in the style macro API), and should be used sparingly. + * Must be imported with `{type: 'macro'}`. + * + * @example + * import {css} from '@react-spectrum/s2/style' with {type: 'macro'}; + * + * const styles = css(` + * backdrop-filter: blur(8px); + * `); + * + * @param content - The CSS declarations to inject. + * @param layer - The CSS `@layer` to place the styles in. Defaults to `'_.a'`. + * @returns The generated class name that applies the styles. + */ +export function css(this: MacroContext | void, content: string, layer = '_.a'): string { + // Check if `this` is undefined, which means style was not called as a macro but as a normal function. + // We also check if this is globalThis, which happens in non-strict mode bundles. + // Also allow style to be called as a normal function in tests. + // @ts-ignore + + if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') { + throw new Error('The css macro must be imported with {type: "macro"}.'); + } + let className = generateArbitraryValueSelector(content, true); + content = `@layer ${layer} { + .${className} { + ${content} + } +}`; + + // Ensure layer is always declared after the _ layer used by style macro. + if (!layer.startsWith('_.')) { + content = `@layer _, ${layer};\n` + content; + } + + if (this && typeof this.addAsset === 'function') { + this.addAsset({ + type: 'css', + content + }); + } + return className; +} + +export function keyframes(this: MacroContext | void, css: string): string { + // Check if `this` is undefined, which means style was not called as a macro but as a normal function. + // We also check if this is globalThis, which happens in non-strict mode bundles. + // Also allow style to be called as a normal function in tests. + // @ts-ignore + + if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') { + throw new Error('The keyframes macro must be imported with {type: "macro"}.'); + } + let name = generateArbitraryValueSelector(css, true); + css = `@keyframes ${name} { + ${css} +}`; + if (this && typeof this.addAsset === 'function') { + this.addAsset({ + type: 'css', + content: css + }); + } + return name; +} diff --git a/packages/@react-spectrum/s2-ai/src/style-utils-copy.ts b/packages/@react-spectrum/s2-ai/src/style-utils-copy.ts index fa4bb418230..0b234b107ac 100644 --- a/packages/@react-spectrum/s2-ai/src/style-utils-copy.ts +++ b/packages/@react-spectrum/s2-ai/src/style-utils-copy.ts @@ -58,6 +58,14 @@ export const widthProperties = ['width', 'minWidth', 'maxWidth'] as const; export const heightProperties = ['size', 'height', 'minHeight', 'maxHeight'] as const; +export const fontProperties = [ + 'font', + 'fontFamily', + 'fontWeight', + 'lineHeight', + 'fontSize' +] as const; + export type StylesProp = StyleString< (typeof allowedOverrides)[number] | (typeof widthProperties)[number] >; @@ -68,6 +76,8 @@ export type StylesPropWithHeight = StyleString< >; export type StylesPropWithoutWidth = StyleString<(typeof allowedOverrides)[number]>; export type UnsafeClassName = string & {properties?: never}; +export type StylesPropWithFont = StyleString<(typeof fontProperties)[number]>; + export interface UnsafeStyles { /** * Sets the CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) @@ -88,8 +98,9 @@ export interface StyleProps extends UnsafeStyles { styles?: StylesProp; } -export function getAllowedOverrides({width = true, height = false} = {}): string[] { +export function getAllowedOverrides({width = true, height = false, font = false} = {}): string[] { return (allowedOverrides as unknown as string[]) .concat(width ? widthProperties : []) - .concat(height ? heightProperties : []); + .concat(height ? heightProperties : []) + .concat(font ? ['fontFamily', 'fontWeight', 'lineHeight', 'fontSize'] : []); } diff --git a/packages/@react-spectrum/s2-ai/stories/MessageSource.stories.tsx b/packages/@react-spectrum/s2-ai/stories/MessageSource.stories.tsx new file mode 100644 index 00000000000..f66a165fdc5 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/stories/MessageSource.stories.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {MessageSource, SourceList, SourceListItem} from '../src/MessageSource'; +import type {Meta, StoryObj} from '@storybook/react'; +import React from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +const meta: Meta = { + component: MessageSource, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'], + argTypes: { + size: { + control: 'radio', + options: ['S', 'M', 'L', 'XL'] + }, + density: { + control: 'radio', + options: ['compact', 'regular', 'spacious'] + }, + isDisabled: { + control: {type: 'boolean'} + }, + children: {table: {disable: true}} + }, + title: 'S2-AI/MessageSource' +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = { + args: { + label: 'Sources' + }, + render: args => { + return ( +

    + + + Hilton email + Market research + User research + + +
    + ); + } +}; diff --git a/packages/@react-spectrum/s2-ai/stories/ResponseStatus.stories.tsx b/packages/@react-spectrum/s2-ai/stories/ResponseStatus.stories.tsx new file mode 100644 index 00000000000..542e85b7ea1 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/stories/ResponseStatus.stories.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import type {Meta, StoryObj} from '@storybook/react'; +import React from 'react'; +import {ResponseStatus, ResponseStatusPanel, ResponseStatusTitle} from '../src/ResponseStatus'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +const meta: Meta = { + component: ResponseStatus, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'], + argTypes: { + size: { + control: 'radio', + options: ['S', 'M', 'L', 'XL'] + }, + density: { + control: 'radio', + options: ['compact', 'regular', 'spacious'] + }, + isLoading: { + control: {type: 'boolean'} + }, + children: {table: {disable: true}} + }, + args: { + isLoading: true + }, + title: 'S2-AI/ResponseStatus' +}; + +export default meta; +type Story = StoryObj; + +export const Example: Story = { + render: args => ( +
    + + + {args.isLoading ? 'Generating response' : 'Response generated'} + + + Here is the generated response content. This area is hidden until the disclosure is + expanded, and cannot be expanded while loading. + + +
    + ) +}; diff --git a/packages/@react-spectrum/s2-ai/ui-icons/Chevron.tsx b/packages/@react-spectrum/s2-ai/ui-icons/Chevron.tsx new file mode 100644 index 00000000000..1db397ac23e --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/Chevron.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import Chevron_L from './S2_ChevronSize200.svg'; +import Chevron_M from './S2_ChevronSize100.svg'; +import Chevron_S from './S2_ChevronSize75.svg'; +import Chevron_XL from './S2_ChevronSize300.svg'; +import Chevron_XS from './S2_ChevronSize50.svg'; +import Chevron_XXL from './S2_ChevronSize400.svg'; +import {ReactNode, SVGProps} from 'react'; +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +let styles = style({ + width: { + size: { + M: 10, + L: 12, + XL: 14, + XXL: 16, + XS: 6, + S: 10 + } + }, + height: { + size: { + M: 10, + L: 12, + XL: 14, + XXL: 16, + XS: 6, + S: 10 + } + } +}); + +export default function Chevron( + props: SVGProps & {size?: 'M' | 'L' | 'XL' | 'XXL' | 'XS' | 'S'} +): ReactNode { + let {size = 'M', ...otherProps} = props; + switch (size) { + case 'M': + return ( + + ); + case 'L': + return ( + + ); + case 'XL': + return ( + + ); + case 'XXL': + return ( + + ); + case 'XS': + return ( + + ); + case 'S': + return ( + + ); + } +} diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize100.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize100.svg new file mode 100644 index 00000000000..2c9651d4a76 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize100.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize200.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize200.svg new file mode 100644 index 00000000000..432a261200a --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize200.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize300.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize300.svg new file mode 100644 index 00000000000..d73f848a1e7 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize300.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize400.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize400.svg new file mode 100644 index 00000000000..6ec8b418664 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize400.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize50.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize50.svg new file mode 100644 index 00000000000..aca7a74e41a --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize50.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize75.svg b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize75.svg new file mode 100644 index 00000000000..ab538918da1 --- /dev/null +++ b/packages/@react-spectrum/s2-ai/ui-icons/S2_ChevronSize75.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/@react-spectrum/s2/src/Disclosure.tsx b/packages/@react-spectrum/s2/src/Disclosure.tsx index 21f510d8d46..1ea0a0baa0c 100644 --- a/packages/@react-spectrum/s2/src/Disclosure.tsx +++ b/packages/@react-spectrum/s2/src/Disclosure.tsx @@ -41,7 +41,12 @@ import { DisclosureProps as RACDisclosureProps } from 'react-aria-components/Disclosure'; import {filterDOMProps} from 'react-aria/filterDOMProps'; -import {getAllowedOverrides, StyleProps, UnsafeStyles} from './style-utils' with {type: 'macro'}; +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'; @@ -139,6 +144,11 @@ export interface DisclosureTitleProps extends UnsafeStyles, DOMProps { level?: number; /** The contents of the disclosure header. */ children: React.ReactNode; + /** + * Spectrum-defined styles, returned by the `style()` macro. Only allows overriding + * `font`, `fontFamily`, `fontWeight`, `fontSize`, and `lineHeight`. + */ + styles?: StylesPropWithFont; } interface DisclosureHeaderProps extends UnsafeStyles, DOMProps { @@ -153,85 +163,88 @@ const headingStyle = style({ minWidth: 0 }); -const buttonStyles = style({ - ...focusRing(), - outlineOffset: -2, - font: 'heading', - color: { - default: baseColor('neutral'), - forcedColors: 'ButtonText', - isDisabled: { - default: 'disabled', - forcedColors: 'GrayText' - } - }, - fontWeight: 'bold', - fontSize: { - size: { - S: 'title-sm', - M: 'title', - L: 'title-lg', - XL: 'title-xl' - } - }, - lineHeight: 'ui', - display: 'flex', - flexGrow: 1, - alignItems: 'baseline', - paddingX: 'calc(self(minHeight) * 3/8 - 1px)', - paddingY: centerPadding(), - gap: 'calc(self(minHeight) * 3/8 - 1px)', - minHeight: { - // compact is equivalent to 'control', but other densities have more padding. - size: { - S: { - density: { - compact: 18, - regular: 24, - spacious: 32 - } - }, - M: { - density: { - compact: 24, - regular: 32, - spacious: 40 - } - }, - L: { - density: { - compact: 32, - regular: 40, - spacious: 48 - } - }, - XL: { - density: { - compact: 40, - regular: 48, - spacious: 56 +const buttonStyles = style( + { + ...focusRing(), + outlineOffset: -2, + font: 'heading', + color: { + default: baseColor('neutral'), + forcedColors: 'ButtonText', + isDisabled: { + default: 'disabled', + forcedColors: 'GrayText' + } + }, + fontWeight: 'bold', + fontSize: { + size: { + S: 'title-sm', + M: 'title', + L: 'title-lg', + XL: 'title-xl' + } + }, + lineHeight: 'ui', + display: 'flex', + flexGrow: 1, + alignItems: 'baseline', + paddingX: 'calc(self(minHeight) * 3/8 - 1px)', + paddingY: centerPadding(), + gap: 'calc(self(minHeight) * 3/8 - 1px)', + minHeight: { + // compact is equivalent to 'control', but other densities have more padding. + size: { + S: { + density: { + compact: 18, + regular: 24, + spacious: 32 + } + }, + M: { + density: { + compact: 24, + regular: 32, + spacious: 40 + } + }, + L: { + density: { + compact: 32, + regular: 40, + spacious: 48 + } + }, + XL: { + density: { + compact: 40, + regular: 48, + spacious: 56 + } } } - } - }, - width: 'full', - backgroundColor: { - default: 'transparent', - isFocusVisible: lightDark('transparent-black-100', 'transparent-white-100'), - isHovered: lightDark('transparent-black-100', 'transparent-white-100'), - isPressed: lightDark('transparent-black-300', 'transparent-white-300') - }, - transition: 'default', - borderWidth: 0, - borderRadius: { - // Only rounded for keyboard focus and quiet. - default: 'none', - isFocusVisible: 'default', - isQuiet: 'default' + }, + width: 'full', + backgroundColor: { + default: 'transparent', + isFocusVisible: lightDark('transparent-black-100', 'transparent-white-100'), + isHovered: lightDark('transparent-black-100', 'transparent-white-100'), + isPressed: lightDark('transparent-black-300', 'transparent-white-300') + }, + transition: 'default', + borderWidth: 0, + borderRadius: { + // Only rounded for keyboard focus and quiet. + default: 'none', + isFocusVisible: 'default', + isQuiet: 'default' + }, + textAlign: 'start', + disableTapHighlight: true }, - textAlign: 'start', - disableTapHighlight: true -}); + getAllowedOverrides({font: true}) +); const chevronStyles = style({ rotate: { @@ -293,7 +306,7 @@ export const DisclosureTitle = forwardRef(function DisclosureTitle( props: DisclosureTitleProps, ref: DOMRef ) { - let {level = 3, UNSAFE_style, UNSAFE_className = '', ...otherProps} = props; + let {level = 3, UNSAFE_style, UNSAFE_className = '', styles, ...otherProps} = props; let domRef = useDOMRef(ref); const domProps = filterDOMProps(otherProps); let {direction} = useLocale(); @@ -309,7 +322,7 @@ export const DisclosureTitle = forwardRef(function DisclosureTitle( style={UNSAFE_style} className={(UNSAFE_className ?? '') + headingStyle}>