diff --git a/.changeset/calm-mosaic-motion.md b/.changeset/calm-mosaic-motion.md new file mode 100644 index 00000000000..3b5a662c249 --- /dev/null +++ b/.changeset/calm-mosaic-motion.md @@ -0,0 +1,5 @@ +--- +'@clerk/mosaic': patch +--- + +Tighten component motion. Input hover and focus now appear instantly and fade on the way out, toasts leave faster than they arrive, hover and press feedback on `Checkbox`, `Radio`, the `Drawer` handle, the switcher trigger and the tag remove button responds instantly, and the `Table` expand icon respects `prefers-reduced-motion`. diff --git a/packages/mosaic/src/components/checkbox/checkbox.styles.ts b/packages/mosaic/src/components/checkbox/checkbox.styles.ts index 6cff123de9c..6487135c039 100644 --- a/packages/mosaic/src/components/checkbox/checkbox.styles.ts +++ b/packages/mosaic/src/components/checkbox/checkbox.styles.ts @@ -35,7 +35,12 @@ export const styles = stylex.create({ flexShrink: 0, opacity: { default: null, ':disabled': 0.5 }, position: 'relative', - transitionDuration: durationVars['--cl-duration-fast'], + transitionDuration: { + default: durationVars['--cl-duration-base'], + ':checked': durationVars['--cl-duration-instant'], + ':indeterminate': durationVars['--cl-duration-instant'], + ':hover': durationVars['--cl-duration-instant'], + }, transitionProperty: 'background-color, border-color', transitionTimingFunction: 'linear', }, diff --git a/packages/mosaic/src/components/drawer/drawer.styles.ts b/packages/mosaic/src/components/drawer/drawer.styles.ts index f6f40cf5a8f..0483de18d84 100644 --- a/packages/mosaic/src/components/drawer/drawer.styles.ts +++ b/packages/mosaic/src/components/drawer/drawer.styles.ts @@ -143,6 +143,10 @@ export const styles = stylex.create({ default: colorVars['--cl-color-border'], ':active': `color-mix(in oklab, ${colorVars['--cl-color-border']}, ${colorVars['--cl-color-foreground-secondary']} 35%)`, }, + '--_cl-grip-duration': { + default: durationVars['--cl-duration-base'], + ':active': durationVars['--cl-duration-instant'], + }, placeItems: 'center', display: 'grid', flexShrink: 0, @@ -155,7 +159,7 @@ export const styles = stylex.create({ backgroundColor: 'var(--_cl-grip-color)', blockSize: space['1.5'], inlineSize: space['11.5'], - transitionDuration: durationVars['--cl-duration-fast'], + transitionDuration: 'var(--_cl-grip-duration)', transitionProperty: 'background-color', transitionTimingFunction: 'linear', }, diff --git a/packages/mosaic/src/components/flow/flow.styles.ts b/packages/mosaic/src/components/flow/flow.styles.ts index ebb4a50e570..39eaff98e33 100644 --- a/packages/mosaic/src/components/flow/flow.styles.ts +++ b/packages/mosaic/src/components/flow/flow.styles.ts @@ -66,7 +66,6 @@ export const styles = stylex.create({ default: easingVars['--cl-ease-enter'], ':where([data-ending-style])': easingVars['--cl-ease-in-out'], }, - willChange: 'transform', minWidth: 0, width: '100%', }, diff --git a/packages/mosaic/src/components/otp/otp.styles.ts b/packages/mosaic/src/components/otp/otp.styles.ts index 42887c5a8e3..09cb3d93ef8 100644 --- a/packages/mosaic/src/components/otp/otp.styles.ts +++ b/packages/mosaic/src/components/otp/otp.styles.ts @@ -2,6 +2,7 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, + durationVars, fontFamilyVars, fontWeightVars, radiusVars, @@ -46,6 +47,7 @@ export const styles = stylex.create({ default: null, ':focus-visible': positiveFocusShadow, }, + transitionDuration: durationVars['--cl-duration-base'], }, touchTarget: { minHeight: { default: null, '@media (pointer: coarse)': targetVars['--cl-target-coarse'] }, diff --git a/packages/mosaic/src/components/radio/radio.styles.ts b/packages/mosaic/src/components/radio/radio.styles.ts index 83017f3f927..be75ad779cb 100644 --- a/packages/mosaic/src/components/radio/radio.styles.ts +++ b/packages/mosaic/src/components/radio/radio.styles.ts @@ -38,7 +38,11 @@ export const styles = stylex.create({ flexShrink: 0, opacity: { default: null, ':disabled': 0.5 }, position: 'relative', - transitionDuration: durationVars['--cl-duration-fast'], + transitionDuration: { + default: durationVars['--cl-duration-base'], + ':checked': durationVars['--cl-duration-instant'], + ':hover': durationVars['--cl-duration-instant'], + }, transitionProperty: 'background-color, border-color', transitionTimingFunction: 'linear', }, diff --git a/packages/mosaic/src/components/table/table.styles.ts b/packages/mosaic/src/components/table/table.styles.ts index 6d2036d8d67..01ade1fcb8d 100644 --- a/packages/mosaic/src/components/table/table.styles.ts +++ b/packages/mosaic/src/components/table/table.styles.ts @@ -3,6 +3,7 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, durationVars, + easingVars, focusVars, fontWeightVars, radiusVars, @@ -156,7 +157,11 @@ export const styles = stylex.create({ [stylex.when.ancestor('[data-expanded]', tableStateMarker)]: 'rotate(90deg)', }, transitionDuration: durationVars['--cl-duration-fast'], - transitionProperty: 'transform', + transitionProperty: { + default: 'transform', + '@media (prefers-reduced-motion: reduce)': 'none', + }, + transitionTimingFunction: easingVars['--cl-ease-default'], }, }); diff --git a/packages/mosaic/src/components/tag-input/tag-input.styles.ts b/packages/mosaic/src/components/tag-input/tag-input.styles.ts index 203b3394dd2..466579c0525 100644 --- a/packages/mosaic/src/components/tag-input/tag-input.styles.ts +++ b/packages/mosaic/src/components/tag-input/tag-input.styles.ts @@ -1,11 +1,19 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, durationVars, fontFamilyVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; +import { + colorVars, + durationVars, + easingVars, + fontFamilyVars, + radiusVars, + space, + typeScaleVars, +} from '../../tokens.stylex'; export const styles = stylex.create({ root: { - '--cl-layout-duration': '200ms', - '--cl-layout-easing': 'ease-out', + '--cl-layout-duration': durationVars['--cl-duration-base'], + '--cl-layout-easing': easingVars['--cl-ease-enter'], padding: space['2'], borderRadius: radiusVars['--cl-radius-md'], gap: space['2'], @@ -49,12 +57,18 @@ export const styles = stylex.create({ ':where([data-starting-style], [data-ending-style])': 'scale(1)', }, }, - transitionDuration: durationVars['--cl-duration-base'], + transitionDuration: { + default: `${durationVars['--cl-duration-fast']}, ${durationVars['--cl-duration-base']}`, + ':where([data-ending-style])': durationVars['--cl-duration-fast'], + }, transitionProperty: { default: 'opacity, transform', '@media (prefers-reduced-motion: reduce)': 'opacity', }, - transitionTimingFunction: { default: 'ease-out', ':where([data-ending-style])': 'ease-in' }, + transitionTimingFunction: { + default: `${easingVars['--cl-ease-enter']}, ${easingVars['--cl-ease-default']}`, + ':where([data-ending-style])': easingVars['--cl-ease-exit'], + }, height: space['6'], maxWidth: '100%', minWidth: 0, @@ -88,7 +102,11 @@ export const styles = stylex.create({ display: 'inline-flex', flexShrink: 0, justifyContent: 'center', - transitionDuration: durationVars['--cl-duration-base'], + transitionDuration: { + default: durationVars['--cl-duration-base'], + ':enabled:active': durationVars['--cl-duration-instant'], + ':enabled:hover': durationVars['--cl-duration-instant'], + }, transitionProperty: 'background-color', transitionTimingFunction: 'linear', height: space['4'], diff --git a/packages/mosaic/src/components/toast/toast.styles.ts b/packages/mosaic/src/components/toast/toast.styles.ts index 2bf2149f655..bc2e80dfd42 100644 --- a/packages/mosaic/src/components/toast/toast.styles.ts +++ b/packages/mosaic/src/components/toast/toast.styles.ts @@ -65,12 +65,18 @@ export const styles = stylex.create({ default: `translateY(calc(var(--toast-index) * -1 * ${stackGap})) scale(calc(1 - var(--toast-index) * 0.05))`, ':where([data-expanded])': `translateY(calc(var(--toast-offset-y) * -1 - var(--toast-index) * ${stackGap}))`, }, - transitionDuration: durationVars['--cl-duration-slow'], + transitionDuration: { + default: durationVars['--cl-duration-slow'], + ':where([data-ending-style])': durationVars['--cl-duration-base'], + }, transitionProperty: { default: 'opacity, transform, translate, height', '@media (prefers-reduced-motion: reduce)': 'opacity', }, - transitionTimingFunction: easingVars['--cl-ease-enter'], + transitionTimingFunction: { + default: easingVars['--cl-ease-enter'], + ':where([data-ending-style])': easingVars['--cl-ease-exit'], + }, translate: { default: '0 0', ':where([data-starting-style], [data-ending-style])': '0 100%', diff --git a/packages/mosaic/src/features/switcher/switcher.styles.ts b/packages/mosaic/src/features/switcher/switcher.styles.ts index 2b104bae86e..3669f1d1f93 100644 --- a/packages/mosaic/src/features/switcher/switcher.styles.ts +++ b/packages/mosaic/src/features/switcher/switcher.styles.ts @@ -17,7 +17,11 @@ export const styles = stylex.create({ backgroundColor: 'transparent', cursor: 'pointer', display: 'inline-flex', - transitionDuration: durationVars['--cl-duration-base'], + transitionDuration: { + default: durationVars['--cl-duration-base'], + ':hover': durationVars['--cl-duration-instant'], + }, + transitionTimingFunction: 'linear', maxWidth: '100%', minWidth: 0, }, diff --git a/packages/mosaic/src/styles/input.styles.ts b/packages/mosaic/src/styles/input.styles.ts index 3a499676c4d..134c89d9dc3 100644 --- a/packages/mosaic/src/styles/input.styles.ts +++ b/packages/mosaic/src/styles/input.styles.ts @@ -1,6 +1,6 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, durationVars, easingVars } from '../tokens.stylex'; +import { colorVars, durationVars } from '../tokens.stylex'; // Focus is marked with a border colour change and a soft box-shadow rather than the // system focus ring, so a text-entry field reads as a field rather than a control. @@ -44,13 +44,12 @@ export const inputStyles = stylex.create({ }, transitionDuration: { default: durationVars['--cl-duration-base'], - ':focus-visible': durationVars['--cl-duration-fast'], + ':focus-visible': durationVars['--cl-duration-instant'], + ':focus-visible:where([aria-invalid="true"])': durationVars['--cl-duration-base'], + ':hover:not([aria-invalid="true"])': durationVars['--cl-duration-instant'], }, transitionProperty: 'color, background-color, border-color, box-shadow', - transitionTimingFunction: { - default: 'linear', - ':focus-visible': `linear, linear, linear, ${easingVars['--cl-ease-default']}`, - }, + transitionTimingFunction: 'linear', }, group: { borderColor: { @@ -86,13 +85,12 @@ export const inputStyles = stylex.create({ }, transitionDuration: { default: durationVars['--cl-duration-base'], - ':has(input:focus-visible, textarea:focus-visible)': durationVars['--cl-duration-fast'], + ':hover:not([data-invalid])': durationVars['--cl-duration-instant'], + ':has(input:focus-visible, textarea:focus-visible)': durationVars['--cl-duration-instant'], + ':has(input:focus-visible, textarea:focus-visible):where([data-invalid])': durationVars['--cl-duration-base'], }, transitionProperty: 'color, background-color, border-color, box-shadow', - transitionTimingFunction: { - default: 'linear', - ':has(input:focus-visible, textarea:focus-visible)': `linear, linear, linear, ${easingVars['--cl-ease-default']}`, - }, + transitionTimingFunction: 'linear', }, disabled: { backgroundColor: disabledBackgroundColor,