diff --git a/.changeset/fair-eggs-cross.md b/.changeset/fair-eggs-cross.md new file mode 100644 index 00000000000..d100afc66f1 --- /dev/null +++ b/.changeset/fair-eggs-cross.md @@ -0,0 +1,7 @@ +--- +'@shopify/polaris': minor +--- + +- Added `tone` prop with `magic` value to `TextField` +- Added `tone` prop with `magic` value to `ChoiceList` +- Added `tone` prop with `magic` value to `Checkbox` diff --git a/polaris-react/src/components/Checkbox/Checkbox.scss b/polaris-react/src/components/Checkbox/Checkbox.scss index 5228729aae8..4183fd7f29c 100644 --- a/polaris-react/src/components/Checkbox/Checkbox.scss +++ b/polaris-react/src/components/Checkbox/Checkbox.scss @@ -30,7 +30,7 @@ box-shadow: inset 0 0 0 var(--p-space-050) var(--p-color-bg-fill-brand); } -// stylelint-disable selector-max-specificity, selector-max-class -- Much easier to read the rules when written like this +// stylelint-disable selector-max-specificity, selector-max-class, selector-max-combinators, max-nesting-depth -- Much easier to read the rules when written like this .Input { // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY @include visually-hidden; @@ -62,7 +62,6 @@ transform var(--p-motion-duration-150) var(--p-motion-ease-out); opacity: 1; - /* stylelint-disable-next-line selector-max-combinators -- need to target svg from icons package */ svg { fill: var(--p-color-text-brand-on-bg-fill); } @@ -88,7 +87,6 @@ } } - // stylelint-disable-next-line selector-max-combinators -- target disabled icon color ~ .Icon svg { color: var(--p-color-checkbox-icon-disabled); } @@ -104,7 +102,37 @@ } } } - // stylelint-enable selector-max-specificity, selector-max-class + + &.toneMagic { + + .Backdrop { + background-color: var(--p-color-bg-surface-magic); + box-shadow: inset 0 0 0 var(--p-border-width-0165) + var(--p-color-border-magic-secondary); + + .ChoiceLabel:hover & { + background-color: var(--p-color-bg-surface-magic-hover); + box-shadow: inset 0 0 0 var(--p-border-width-0165) + var(--p-color-border-magic-secondary-hover); + } + } + + &:checked, + &.Input-indeterminate { + + .Backdrop { + border-color: var(--p-color-bg-fill-magic); + background-color: var(--p-color-bg-fill-magic); + box-shadow: inset 0 0 0 var(--p-space-800) var(--p-color-bg-fill-magic); + + .ChoiceLabel:hover & { + border-color: var(--p-color-bg-fill-magic); + background-color: var(--p-color-bg-fill-magic); + box-shadow: inset 0 0 0 var(--p-space-800) + var(--p-color-bg-fill-magic); + } + } + } + } + // stylelint-enable selector-max-specificity, selector-max-class, selector-max-combinators, max-nesting-depth } .Backdrop { @@ -207,7 +235,7 @@ } } } -// stylelint-enable selector-max-specificity, selector-max-class, selector-max-combinators, selector-max-compound-selectors +// stylelint-enable selector-max-specificity, selector-max-class, selector-max-combinators // stylelint-disable-next-line selector-max-combinators, selector-max-type -- override .animated svg > path { diff --git a/polaris-react/src/components/Checkbox/Checkbox.stories.tsx b/polaris-react/src/components/Checkbox/Checkbox.stories.tsx index 7156958daeb..522e71da3d6 100644 --- a/polaris-react/src/components/Checkbox/Checkbox.stories.tsx +++ b/polaris-react/src/components/Checkbox/Checkbox.stories.tsx @@ -77,6 +77,20 @@ export function Error() { ); } +export function Magic() { + const [checked, setChecked] = useState(); + const handleChange = useCallback((newChecked) => setChecked(newChecked), []); + + return ( + + ); +} + export function WithBleedAndFill() { const [checked1, setChecked1] = useState(false); const [checked2, setChecked2] = useState(false); diff --git a/polaris-react/src/components/Checkbox/Checkbox.tsx b/polaris-react/src/components/Checkbox/Checkbox.tsx index 7d7a0c90fd2..eee6141f744 100644 --- a/polaris-react/src/components/Checkbox/Checkbox.tsx +++ b/polaris-react/src/components/Checkbox/Checkbox.tsx @@ -7,7 +7,7 @@ import React, { } from 'react'; import {MinusMinor} from '@shopify/polaris-icons'; -import {classNames} from '../../utilities/css'; +import {classNames, variationName} from '../../utilities/css'; import type {ResponsiveProp} from '../../utilities/css'; import type {ChoiceBleedProps} from '../Choice'; import {Choice, helpTextID} from '../Choice'; @@ -51,6 +51,8 @@ export interface CheckboxProps extends ChoiceBleedProps { helpText?: React.ReactNode; /** Display an error message */ error?: Error | boolean; + /** Indicates the tone of the checkbox */ + tone?: 'magic'; } export const Checkbox = forwardRef( @@ -77,6 +79,7 @@ export const Checkbox = forwardRef( bleedBlockEnd, bleedInlineStart, bleedInlineEnd, + tone, }: CheckboxProps, ref, ) { @@ -153,6 +156,7 @@ export const Checkbox = forwardRef( const inputClassName = classNames( styles.Input, isIndeterminate && styles['Input-indeterminate'], + tone && styles[variationName('tone', tone)], ); const extraChoiceProps = { @@ -173,6 +177,7 @@ export const Checkbox = forwardRef( disabled={disabled} labelClassName={classNames(styles.ChoiceLabel, labelClassName)} fill={fill} + tone={tone} {...extraChoiceProps} > diff --git a/polaris-react/src/components/Choice/Choice.scss b/polaris-react/src/components/Choice/Choice.scss index 1ea088bdbda..2b568c5f0c7 100644 --- a/polaris-react/src/components/Choice/Choice.scss +++ b/polaris-react/src/components/Choice/Choice.scss @@ -114,6 +114,10 @@ } } +.toneMagic > .Label { + color: var(--p-color-text-magic); +} + .disabled + .Descriptions { // the component in the HelpText markup in Choice.tsx is set to `undefined` when the disabled prop is true // Which tells it to inherit whatever color we specify here. diff --git a/polaris-react/src/components/Choice/Choice.tsx b/polaris-react/src/components/Choice/Choice.tsx index 863590c7ff3..3464d28efdd 100644 --- a/polaris-react/src/components/Choice/Choice.tsx +++ b/polaris-react/src/components/Choice/Choice.tsx @@ -6,6 +6,7 @@ import { getResponsiveValue, classNames, sanitizeCustomProperties, + variationName, } from '../../utilities/css'; import type {ResponsiveProp} from '../../utilities/css'; import type {Error} from '../../types'; @@ -70,6 +71,8 @@ interface ChoiceProps extends ChoiceBleedProps { error?: Error | boolean; /** Additional text to aide in use. Will add a wrapping
*/ helpText?: React.ReactNode; + /** Indicates the tone of the choice */ + tone?: 'magic'; } export function Choice({ @@ -88,11 +91,13 @@ export function Choice({ bleedBlockEnd, bleedInlineStart, bleedInlineEnd, + tone, }: ChoiceProps) { const className = classNames( styles.Choice, labelHidden && styles.labelHidden, disabled && styles.disabled, + tone && styles[variationName('tone', tone)], labelClassName, ); diff --git a/polaris-react/src/components/ChoiceList/ChoiceList.stories.tsx b/polaris-react/src/components/ChoiceList/ChoiceList.stories.tsx index 4b191f6bc2a..e3c6424478b 100644 --- a/polaris-react/src/components/ChoiceList/ChoiceList.stories.tsx +++ b/polaris-react/src/components/ChoiceList/ChoiceList.stories.tsx @@ -45,6 +45,26 @@ export function WithError() { ); } +export function Magic() { + const [selected, setSelected] = useState(['hidden']); + + const handleChange = useCallback((value) => setSelected(value), []); + + return ( + + ); +} + export function WithMultiChoice() { const [selected, setSelected] = useState(['hidden']); @@ -74,6 +94,36 @@ export function WithMultiChoice() { ); } +export function MagicWithMultiChoice() { + const [selected, setSelected] = useState(['hidden']); + + const handleChange = useCallback((value) => setSelected(value), []); + + return ( + + ); +} + export function WithChildrenContent() { const [selected, setSelected] = useState(['none']); const [textFieldValue, setTextFieldValue] = useState(''); diff --git a/polaris-react/src/components/ChoiceList/ChoiceList.tsx b/polaris-react/src/components/ChoiceList/ChoiceList.tsx index 5f8c1f4b307..6226bdbdec0 100644 --- a/polaris-react/src/components/ChoiceList/ChoiceList.tsx +++ b/polaris-react/src/components/ChoiceList/ChoiceList.tsx @@ -46,6 +46,8 @@ export interface ChoiceListProps { disabled?: boolean; /** Callback when the selected choices change */ onChange?(selected: string[], name: string): void; + /** Indicates the tone of the choice list */ + tone?: 'magic'; } export function ChoiceList({ @@ -58,6 +60,7 @@ export function ChoiceList({ error, disabled = false, name: nameProp, + tone, }: ChoiceListProps) { // Type asserting to any is required for TS3.2 but can be removed when we update to 3.3 // see https://github.com/Microsoft/TypeScript/issues/28768 @@ -119,6 +122,7 @@ export function ChoiceList({ ariaDescribedBy={ error && describedByError ? errorTextID(finalName) : null } + tone={tone} /> {children} diff --git a/polaris-react/src/components/RadioButton/RadioButton.scss b/polaris-react/src/components/RadioButton/RadioButton.scss index 8a76884875b..8c33cd76e0f 100644 --- a/polaris-react/src/components/RadioButton/RadioButton.scss +++ b/polaris-react/src/components/RadioButton/RadioButton.scss @@ -47,6 +47,21 @@ } } + &.toneMagic:checked:not([disabled]) + .Backdrop { + &, + .ChoiceLabel:hover & { + background-color: var(--p-color-bg-fill-magic); + border-color: var(--p-color-bg-fill-magic); + } + + &::before { + &, + .ChoiceLabel:hover & { + background-color: var(--p-color-text-magic-on-bg-fill); + } + } + } + + .Backdrop { .ChoiceLabel:hover & { cursor: pointer; diff --git a/polaris-react/src/components/RadioButton/RadioButton.stories.tsx b/polaris-react/src/components/RadioButton/RadioButton.stories.tsx index a25e70c6322..37e76022991 100644 --- a/polaris-react/src/components/RadioButton/RadioButton.stories.tsx +++ b/polaris-react/src/components/RadioButton/RadioButton.stories.tsx @@ -70,6 +70,38 @@ export function DisabledRadio() { ); } +export function Magic() { + const [value, setValue] = useState('disabled'); + + const handleChange = useCallback( + (_checked, newValue) => setValue(newValue), + [], + ); + + return ( + + + + + ); +} + export function WithBleed() { const [value1, setValue1] = useState('disabled'); const [value2, setValue2] = useState('disabled2'); diff --git a/polaris-react/src/components/RadioButton/RadioButton.tsx b/polaris-react/src/components/RadioButton/RadioButton.tsx index 992e9eb140a..0afbe3a7723 100644 --- a/polaris-react/src/components/RadioButton/RadioButton.tsx +++ b/polaris-react/src/components/RadioButton/RadioButton.tsx @@ -1,6 +1,6 @@ import React, {useRef, useId} from 'react'; -import {classNames} from '../../utilities/css'; +import {classNames, variationName} from '../../utilities/css'; import type {ResponsiveProp} from '../../utilities/css'; import {Choice, helpTextID} from '../Choice'; import type {ChoiceBleedProps} from '../Choice'; @@ -34,6 +34,8 @@ export interface RadioButtonProps extends ChoiceBleedProps { fill?: ResponsiveProp; /** Additional text to aide in use */ helpText?: React.ReactNode; + /** Indicates the tone of the text field */ + tone?: 'magic'; } export function RadioButton({ @@ -55,6 +57,7 @@ export function RadioButton({ bleedBlockEnd, bleedInlineStart, bleedInlineEnd, + tone, }: RadioButtonProps) { const uniqId = useId(); const id = idProp ?? uniqId; @@ -80,7 +83,10 @@ export function RadioButton({ ? describedBy.join(' ') : undefined; - const inputClassName = classNames(styles.Input); + const inputClassName = classNames( + styles.Input, + tone && styles[variationName('tone', tone)], + ); const extraChoiceProps = { helpText, @@ -100,6 +106,7 @@ export function RadioButton({ labelClassName={styles.ChoiceLabel} fill={fill} {...extraChoiceProps} + {...(checked ? {tone} : {})} > .Input { + color: var(--p-color-text-magic); + } + + > .Backdrop { + background-color: var(--p-color-bg-surface-magic); + border-color: var(--p-color-border-magic-secondary); + } + + &:not(.disabled):not(.error):not(.readOnly) + > .Input:hover:not(:focus-visible) { + ~ .Backdrop { + background-color: var(--p-color-bg-surface-magic-hover); + border-color: var(--p-color-border-magic-secondary-hover); + } + } + + &.focus > .Input, + &.focus > .VerticalContent, + &.TextField:focus-within > .Input, + &.Input:focus-visible { + color: var(--p-color-text); + } +} +// stylelint-enable + .disabled { color: var(--p-color-text-disabled); cursor: initial; diff --git a/polaris-react/src/components/TextField/TextField.stories.tsx b/polaris-react/src/components/TextField/TextField.stories.tsx index 62cce032b3b..898522c867e 100644 --- a/polaris-react/src/components/TextField/TextField.stories.tsx +++ b/polaris-react/src/components/TextField/TextField.stories.tsx @@ -41,6 +41,22 @@ export function Default() { ); } +export function Magic() { + const [value, setValue] = useState('Jaded Pixel'); + + const handleChange = useCallback((newValue) => setValue(newValue), []); + + return ( + + ); +} + export function Number() { const [value, setValue] = useState('1.0'); const [value1, setValue1] = useState('1.0'); diff --git a/polaris-react/src/components/TextField/TextField.tsx b/polaris-react/src/components/TextField/TextField.tsx index b28f52d6692..72d5413d49c 100644 --- a/polaris-react/src/components/TextField/TextField.tsx +++ b/polaris-react/src/components/TextField/TextField.tsx @@ -175,6 +175,8 @@ interface NonMutuallyExclusiveProps { onFocus?: (event?: React.FocusEvent) => void; /** Callback fired when input is blurred */ onBlur?(event?: React.FocusEvent): void; + /** Indicates the tone of the text field */ + tone?: 'magic'; } export type MutuallyExclusiveSelectionProps = @@ -241,6 +243,7 @@ export function TextField({ onSpinnerChange, onFocus, onBlur, + tone, }: TextFieldProps) { const i18n = useI18n(); const [height, setHeight] = useState(null); @@ -290,6 +293,7 @@ export function TextField({ disabled && styles.disabled, readOnly && styles.readOnly, error && styles.error, + tone && styles[variationName('tone', tone)], multiline && styles.multiline, focus && !disabled && styles.focus, variant !== 'inherit' && styles[variant], diff --git a/polaris-tokens/src/themes/base/color.ts b/polaris-tokens/src/themes/base/color.ts index 2b8453be563..f1cfa271281 100644 --- a/polaris-tokens/src/themes/base/color.ts +++ b/polaris-tokens/src/themes/base/color.ts @@ -134,6 +134,7 @@ export type ColorBorderAlias = | 'border-inverse-hover' | 'border-inverse' | 'border-magic-secondary' + | 'border-magic-secondary-hover' | 'border-magic' | 'border-secondary' | 'border-success' @@ -202,6 +203,7 @@ export type ColorTextAlias = | 'text-link' | 'text-magic-on-bg-fill' | 'text-magic' + | 'text-magic-secondary' | 'text-secondary' | 'text-success-active' | 'text-success-hover' @@ -399,15 +401,15 @@ export const color: { 'The active state (on press) color for elements indicating areas of focus in editors.', }, 'color-bg-surface-magic': { - value: colors.purple[3], + value: colors.purple[2], description: 'Use for backgrounds of elements suggested by magic AI.', }, 'color-bg-surface-magic-hover': { - value: colors.purple[4], + value: colors.purple[3], description: 'The hover state color for elements suggested by magic AI.', }, 'color-bg-surface-magic-active': { - value: colors.purple[6], + value: colors.purple[5], description: 'The active state (on press) color for elements suggested by magic AI.', }, @@ -851,6 +853,11 @@ export const color: { value: colors.purple[14], description: 'Use for text suggested by magic AI.', }, + 'color-text-magic-secondary': { + value: colors.purple[12], + description: + 'Use for text suggested by magic AI with a secondary level of prominence.', + }, 'color-text-magic-on-bg-fill': { value: colors.purple[1], description: 'Use for text and icons on bg-fill-magic.', @@ -936,10 +943,15 @@ export const color: { 'The active state (on press) color for borders indicating areas of focus.', }, 'color-border-magic': { - value: colors.purple[10], + value: colors.purple[6], description: 'Use for borders suggested by magic AI.', }, 'color-border-magic-secondary': { + value: colors.purple[11], + description: + 'Use for borders suggested by magic AI, such as borders on text fields.', + }, + 'color-border-magic-secondary-hover': { value: colors.purple[12], description: 'Use for borders suggested by magic AI, such as borders on text fields.',