@cdx-ui/components 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +8 -12
  2. package/lib/commonjs/components/Input/styles.js +12 -4
  3. package/lib/commonjs/components/Input/styles.js.map +1 -1
  4. package/lib/commonjs/components/InsuranceDisclosure/FdicLockup.js +31 -0
  5. package/lib/commonjs/components/InsuranceDisclosure/FdicLockup.js.map +1 -0
  6. package/lib/commonjs/components/InsuranceDisclosure/index.js +22 -4
  7. package/lib/commonjs/components/InsuranceDisclosure/index.js.map +1 -1
  8. package/lib/commonjs/components/InsuranceDisclosure/styles.js +13 -3
  9. package/lib/commonjs/components/InsuranceDisclosure/styles.js.map +1 -1
  10. package/lib/commonjs/components/Radio/index.js +14 -3
  11. package/lib/commonjs/components/Radio/index.js.map +1 -1
  12. package/lib/commonjs/components/Radio/styles.js +24 -8
  13. package/lib/commonjs/components/Radio/styles.js.map +1 -1
  14. package/lib/commonjs/components/Select/styles.js +11 -3
  15. package/lib/commonjs/components/Select/styles.js.map +1 -1
  16. package/lib/commonjs/components/Switch/styles.js +14 -14
  17. package/lib/commonjs/components/Switch/styles.js.map +1 -1
  18. package/lib/commonjs/components/Textarea/styles.js +4 -2
  19. package/lib/commonjs/components/Textarea/styles.js.map +1 -1
  20. package/lib/commonjs/figma/Field.Radio.figma.js +58 -0
  21. package/lib/commonjs/figma/Field.Radio.figma.js.map +1 -0
  22. package/lib/commonjs/figma/InsuranceDisclosure.figma.js +26 -0
  23. package/lib/commonjs/figma/InsuranceDisclosure.figma.js.map +1 -0
  24. package/lib/commonjs/figma/Radio.Group.figma.js +24 -0
  25. package/lib/commonjs/figma/Radio.Group.figma.js.map +1 -0
  26. package/lib/commonjs/figma/Radio.figma.js +12 -6
  27. package/lib/commonjs/figma/Radio.figma.js.map +1 -1
  28. package/lib/commonjs/styles/constants.js +5 -1
  29. package/lib/commonjs/styles/constants.js.map +1 -1
  30. package/lib/module/components/Input/styles.js +13 -5
  31. package/lib/module/components/Input/styles.js.map +1 -1
  32. package/lib/module/components/InsuranceDisclosure/FdicLockup.js +27 -0
  33. package/lib/module/components/InsuranceDisclosure/FdicLockup.js.map +1 -0
  34. package/lib/module/components/InsuranceDisclosure/index.js +22 -4
  35. package/lib/module/components/InsuranceDisclosure/index.js.map +1 -1
  36. package/lib/module/components/InsuranceDisclosure/styles.js +13 -3
  37. package/lib/module/components/InsuranceDisclosure/styles.js.map +1 -1
  38. package/lib/module/components/Radio/index.js +14 -3
  39. package/lib/module/components/Radio/index.js.map +1 -1
  40. package/lib/module/components/Radio/styles.js +25 -9
  41. package/lib/module/components/Radio/styles.js.map +1 -1
  42. package/lib/module/components/Select/styles.js +12 -4
  43. package/lib/module/components/Select/styles.js.map +1 -1
  44. package/lib/module/components/Switch/styles.js +14 -14
  45. package/lib/module/components/Switch/styles.js.map +1 -1
  46. package/lib/module/components/Textarea/styles.js +5 -3
  47. package/lib/module/components/Textarea/styles.js.map +1 -1
  48. package/lib/module/figma/Field.Radio.figma.js +52 -0
  49. package/lib/module/figma/Field.Radio.figma.js.map +1 -0
  50. package/lib/module/figma/InsuranceDisclosure.figma.js +20 -0
  51. package/lib/module/figma/InsuranceDisclosure.figma.js.map +1 -0
  52. package/lib/module/figma/Radio.Group.figma.js +18 -0
  53. package/lib/module/figma/Radio.Group.figma.js.map +1 -0
  54. package/lib/module/figma/Radio.figma.js +12 -6
  55. package/lib/module/figma/Radio.figma.js.map +1 -1
  56. package/lib/module/styles/constants.js +4 -0
  57. package/lib/module/styles/constants.js.map +1 -1
  58. package/lib/typescript/components/Input/styles.d.ts.map +1 -1
  59. package/lib/typescript/components/InsuranceDisclosure/FdicLockup.d.ts +2 -0
  60. package/lib/typescript/components/InsuranceDisclosure/FdicLockup.d.ts.map +1 -0
  61. package/lib/typescript/components/InsuranceDisclosure/index.d.ts +24 -3
  62. package/lib/typescript/components/InsuranceDisclosure/index.d.ts.map +1 -1
  63. package/lib/typescript/components/InsuranceDisclosure/styles.d.ts +5 -1
  64. package/lib/typescript/components/InsuranceDisclosure/styles.d.ts.map +1 -1
  65. package/lib/typescript/components/Radio/index.d.ts.map +1 -1
  66. package/lib/typescript/components/Radio/styles.d.ts.map +1 -1
  67. package/lib/typescript/components/Select/styles.d.ts.map +1 -1
  68. package/lib/typescript/components/Switch/styles.d.ts.map +1 -1
  69. package/lib/typescript/components/Textarea/styles.d.ts.map +1 -1
  70. package/lib/typescript/figma/Field.Radio.figma.d.ts +8 -0
  71. package/lib/typescript/figma/Field.Radio.figma.d.ts.map +1 -0
  72. package/lib/typescript/figma/InsuranceDisclosure.figma.d.ts +8 -0
  73. package/lib/typescript/figma/InsuranceDisclosure.figma.d.ts.map +1 -0
  74. package/lib/typescript/figma/Radio.Group.figma.d.ts +8 -0
  75. package/lib/typescript/figma/Radio.Group.figma.d.ts.map +1 -0
  76. package/lib/typescript/figma/Radio.figma.d.ts.map +1 -1
  77. package/lib/typescript/styles/constants.d.ts +1 -0
  78. package/lib/typescript/styles/constants.d.ts.map +1 -1
  79. package/package.json +5 -5
  80. package/src/components/Input/styles.ts +11 -3
  81. package/src/components/InsuranceDisclosure/FdicLockup.tsx +23 -0
  82. package/src/components/InsuranceDisclosure/index.tsx +54 -14
  83. package/src/components/InsuranceDisclosure/styles.ts +28 -9
  84. package/src/components/Radio/index.tsx +22 -6
  85. package/src/components/Radio/styles.ts +46 -6
  86. package/src/components/Select/styles.ts +11 -3
  87. package/src/components/Switch/styles.ts +23 -15
  88. package/src/components/Textarea/styles.ts +5 -3
  89. package/src/styles/constants.ts +4 -0
@@ -0,0 +1,23 @@
1
+ import { Path, Svg } from 'react-native-svg';
2
+
3
+ const FDIC_NAVY = '#003366';
4
+
5
+ export function FdicLockup() {
6
+ return (
7
+ <Svg width="100%" height="100%" viewBox="0 0 44 20" fill="none">
8
+ <Path
9
+ d="M5.39953 18.3086H0.896923V1.73399H10.1587V6.06628H5.39953V8.7493H9.88382V13.0821H5.39953V18.3086Z"
10
+ fill={FDIC_NAVY}
11
+ />
12
+ <Path d="M30.228 1.69895H25.7254V18.308H30.228V1.69895Z" fill={FDIC_NAVY} />
13
+ <Path
14
+ d="M39.4208 13.924C37.2579 13.924 35.4679 12.1616 35.4679 10.0003C35.4679 7.83897 37.2579 6.07598 39.4208 6.07598C41.3929 6.07598 42.424 7.12483 43.1031 8.50623V2.55916C43.1031 2.55916 41.1094 1.665 39.2516 1.665C35.1252 1.665 30.6543 5.46047 30.6543 10.0003C30.6543 15.5582 34.9187 18.335 39.2516 18.335C41.1089 18.335 43.1031 17.4419 43.1031 17.4419V11.5466C42.2903 12.9479 41.3821 13.924 39.4208 13.924Z"
15
+ fill={FDIC_NAVY}
16
+ />
17
+ <Path
18
+ d="M15.3172 1.73399C18.1366 1.73399 24.946 2.42118 24.946 10.0132C24.946 17.6209 18.8254 18.2935 15.3172 18.293H10.7785V1.73399H15.3172ZM20.4752 9.98679C20.4752 7.02943 18.1027 5.99783 15.3102 5.99783V13.9758C18.1027 13.9758 20.4752 12.9436 20.4752 9.98679Z"
19
+ fill={FDIC_NAVY}
20
+ />
21
+ </Svg>
22
+ );
23
+ }
@@ -5,49 +5,89 @@ import { cn } from '@cdx-ui/utils';
5
5
  import { Image as ForgeImage } from '../Image/Image';
6
6
  import { HStack, type HStackProps } from '../Stack/HStack';
7
7
  import { Text } from '../Text';
8
+ import { FdicLockup } from './FdicLockup';
8
9
  import {
9
10
  insuranceDisclosureLogoVariants,
10
11
  insuranceDisclosureRootVariants,
11
12
  insuranceDisclosureTextVariants,
12
13
  } from './styles';
13
14
 
14
- export interface InsuranceDisclosureProps extends Omit<HStackProps, 'children'> {
15
- logoSource: string | ImageSourcePropType;
16
- logoAccessibilityLabel: string;
15
+ type InsuranceDisclosureSharedProps = Omit<HStackProps, 'children'> & {
16
+ /**
17
+ * When `true`, renders surface chrome (background, border, radius, padding).
18
+ * Defaults to `false` — unframed disclosures have no border or padding.
19
+ *
20
+ * @breakingChange Prior versions always rendered a bordered container with `p-4`.
21
+ * Pass `framed` to restore chrome; padding is now `px-3 py-2`.
22
+ */
23
+ framed?: boolean;
17
24
  label?: string;
18
25
  className?: string;
26
+ };
27
+
28
+ /** Bundled official FDIC lockup — omit `logoSource` to use the default mark. */
29
+ export interface InsuranceDisclosureBundledLogoProps extends InsuranceDisclosureSharedProps {
30
+ logoSource?: undefined;
31
+ /** Ignored when the bundled FDIC lockup is used. */
32
+ logoAccessibilityLabel?: string;
33
+ }
34
+
35
+ /** Custom logo override — `logoAccessibilityLabel` is required. */
36
+ export interface InsuranceDisclosureCustomLogoProps extends InsuranceDisclosureSharedProps {
37
+ /** Logo image — a remote URI string or a React Native image source. */
38
+ logoSource: string | ImageSourcePropType;
39
+ /** Accessible label for the custom logo, announced by assistive technology. */
40
+ logoAccessibilityLabel: string;
19
41
  }
20
42
 
43
+ export type InsuranceDisclosureProps =
44
+ | InsuranceDisclosureBundledLogoProps
45
+ | InsuranceDisclosureCustomLogoProps;
46
+
21
47
  export const InsuranceDisclosure = forwardRef<
22
48
  ComponentRef<typeof HStack>,
23
49
  InsuranceDisclosureProps
24
50
  >(function InsuranceDisclosure(
25
- { logoSource, logoAccessibilityLabel, label, className, style, ...props },
51
+ { framed = false, logoSource, logoAccessibilityLabel, label, className, style, ...props },
26
52
  ref,
27
53
  ) {
54
+ const useCustomLogo = logoSource != null;
55
+
28
56
  return (
29
57
  <HStack
30
58
  ref={ref}
31
59
  space="md"
32
- className={cn(insuranceDisclosureRootVariants(), className)}
60
+ className={cn(insuranceDisclosureRootVariants({ framed }), className)}
33
61
  style={style}
34
62
  {...props}
35
63
  {...mergeDataAttributes(props, dataAttributes({ slot: 'insurance-disclosure' }))}
36
64
  >
37
- <ForgeImage
38
- source={logoSource}
39
- accessibilityLabel={logoAccessibilityLabel}
40
- accessible
41
- resizeMode="contain"
42
- className={insuranceDisclosureLogoVariants()}
43
- {...mergeDataAttributes({}, dataAttributes({ slot: 'insurance-disclosure-logo' }))}
44
- />
65
+ {useCustomLogo ? (
66
+ <ForgeImage
67
+ source={logoSource}
68
+ accessibilityLabel={logoAccessibilityLabel}
69
+ accessible={logoAccessibilityLabel != null}
70
+ resizeMode="contain"
71
+ className={insuranceDisclosureLogoVariants()}
72
+ {...mergeDataAttributes({}, dataAttributes({ slot: 'insurance-disclosure-logo' }))}
73
+ />
74
+ ) : (
75
+ <View
76
+ accessible
77
+ accessibilityRole="image"
78
+ accessibilityLabel="FDIC"
79
+ className={insuranceDisclosureLogoVariants()}
80
+ {...mergeDataAttributes({}, dataAttributes({ slot: 'insurance-disclosure-logo' }))}
81
+ >
82
+ <FdicLockup />
83
+ </View>
84
+ )}
45
85
  {label ? (
46
86
  <View
47
87
  className="min-w-0 shrink"
48
88
  {...mergeDataAttributes({}, dataAttributes({ slot: 'insurance-disclosure-content' }))}
49
89
  >
50
- <Text size="xs" className={insuranceDisclosureTextVariants()}>
90
+ <Text size="sm" className={insuranceDisclosureTextVariants()}>
51
91
  {label}
52
92
  </Text>
53
93
  </View>
@@ -1,12 +1,31 @@
1
- import { cva } from '@cdx-ui/utils';
1
+ import { cva, type VariantProps } from '@cdx-ui/utils';
2
2
 
3
- export const insuranceDisclosureRootVariants = cva([
4
- 'w-full max-w-full items-center justify-start',
5
- 'border border-stroke-secondary',
6
- 'rounded-[var(--border-radius-default)]',
7
- 'p-4',
8
- ]);
3
+ export const insuranceDisclosureRootVariants = cva(
4
+ ['w-full max-w-full items-center justify-start'],
5
+ {
6
+ variants: {
7
+ framed: {
8
+ true: [
9
+ 'bg-surface-primary',
10
+ 'border border-stroke-secondary',
11
+ 'rounded-[var(--border-radius-default)]',
12
+ 'px-3 py-2',
13
+ ],
14
+ false: '',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ framed: false,
19
+ },
20
+ },
21
+ );
22
+
23
+ export type InsuranceDisclosureRootVariantProps = VariantProps<
24
+ typeof insuranceDisclosureRootVariants
25
+ >;
9
26
 
10
- export const insuranceDisclosureLogoVariants = cva(['h-[23px] w-[37px]']);
27
+ export const insuranceDisclosureLogoVariants = cva(['h-5 w-11 shrink-0']);
11
28
 
12
- export const insuranceDisclosureTextVariants = cva(['shrink text-content-secondary']);
29
+ export const insuranceDisclosureTextVariants = cva([
30
+ 'shrink text-content-primary italic [font-family:var(--font-body-italic)]',
31
+ ]);
@@ -74,10 +74,11 @@ const RadioIndicator = forwardRef<View, RadioIndicatorProps>(
74
74
  useRadioContext();
75
75
 
76
76
  const checkState = isChecked ? 'checked' : 'unchecked';
77
- const fieldState =
78
- INDICATOR_STATES[
79
- `${checkState}:${isInvalid ? 'true' : 'false'}:${isPressed ? 'true' : 'false'}`
80
- ];
77
+ const fieldState = isReadOnly
78
+ ? (`readonly-${checkState}${isInvalid ? '-invalid' : ''}` as const)
79
+ : INDICATOR_STATES[
80
+ `${checkState}:${isInvalid ? 'true' : 'false'}:${isPressed ? 'true' : 'false'}`
81
+ ];
81
82
 
82
83
  const computedClassName = cn(radioIndicatorVariants(), className);
83
84
  const Comp = asChild ? Slot : View;
@@ -104,11 +105,18 @@ const RadioIndicator = forwardRef<View, RadioIndicatorProps>(
104
105
  )}
105
106
  >
106
107
  <View
107
- className={cn(radioInnerDotVariants())}
108
+ collapsable={false}
109
+ className={cn(
110
+ radioInnerDotVariants(),
111
+ // Native: unconditional bg so Uniwind does not have to win a data-attribute
112
+ // background over `bg-surface-action-strong` (web keeps compound selectors).
113
+ isReadOnly && isChecked && !isInvalid && 'bg-content-primary',
114
+ )}
108
115
  {...dataAttributes({
109
116
  checked: isChecked,
110
117
  invalid: isInvalid,
111
118
  hover: isHovered,
119
+ readonly: isReadOnly,
112
120
  fieldState,
113
121
  })}
114
122
  />
@@ -132,6 +140,7 @@ export interface RadioLabelProps extends TextProps {
132
140
 
133
141
  const RadioLabel = forwardRef<Text, RadioLabelProps>(
134
142
  ({ asChild, className, children, style, ...props }, ref) => {
143
+ const { isDisabled, isReadOnly } = useRadioContext();
135
144
  const computedClassName = cn(radioLabelVariants(), className);
136
145
  const Comp = asChild ? Slot : Text;
137
146
 
@@ -141,7 +150,14 @@ const RadioLabel = forwardRef<Text, RadioLabelProps>(
141
150
  className={computedClassName}
142
151
  style={style}
143
152
  {...props}
144
- {...mergeDataAttributes(props, dataAttributes({ slot: 'radio-label' }))}
153
+ {...mergeDataAttributes(
154
+ props,
155
+ dataAttributes({
156
+ slot: 'radio-label',
157
+ disabled: isDisabled,
158
+ readonly: isReadOnly,
159
+ }),
160
+ )}
145
161
  >
146
162
  {children}
147
163
  </Comp>
@@ -1,11 +1,12 @@
1
1
  import { Platform } from 'react-native';
2
2
  import { cva } from '@cdx-ui/utils';
3
- import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
3
+ import { DISABLED_CURSOR, DISABLED_OPACITY, TRANSITION_COLORS } from '../../styles/constants';
4
4
 
5
5
  export const radioRootVariants = cva([
6
6
  'flex-row items-center gap-2',
7
- 'data-[disabled=true]:opacity-[var(--opacity-disabled)]',
7
+ DISABLED_OPACITY,
8
8
  DISABLED_CURSOR,
9
+ 'data-[readonly=true]:cursor-default',
9
10
  // Suppress default :focus-visible outline on the role="radio" Pressable;
10
11
  // focus ring is drawn on the indicator instead of wrapping the whole control.
11
12
  'web:outline-none web:focus:outline-none web:focus-visible:outline-none',
@@ -16,7 +17,8 @@ export const radioIndicatorVariants = cva([
16
17
  'rounded-[var(--border-radius-round)]',
17
18
  'border-[length:var(--border-width-selected)]',
18
19
  'h-5 w-5',
19
- 'bg-surface-primary',
20
+ // Figma: indicator fill is transparent; selection reads via border + inner dot only.
21
+ 'bg-transparent',
20
22
  'data-[checked=false]:border-content-secondary',
21
23
  'data-[checked=true]:border-stroke-action',
22
24
  'data-[invalid=true]:border-stroke-danger',
@@ -36,12 +38,32 @@ export const radioIndicatorVariants = cva([
36
38
  'web:active:data-[hover=true]:data-[invalid=true]:data-[checked=false]:border-surface-danger-strong-active',
37
39
  'web:active:data-[hover=true]:data-[checked=true]:border-surface-action-strong-active',
38
40
  'web:active:data-[hover=true]:data-[checked=true]:data-[invalid=true]:border-surface-danger-strong-active',
41
+ // Focus ring — applies to read-only as well (focusable + aria-readonly).
39
42
  'web:data-[focus-visible=true]:outline-none',
40
43
  'web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-stroke-focus web:data-[focus-visible=true]:ring-offset-2',
44
+ // Read-only — Figma: transparent fill + content-tertiary border (checked and unchecked).
45
+ // After hover/active so equal-or-higher specificity keeps the static treatment.
46
+ // Invalid is not a Figma readonly variant; retain danger so error remains visible.
47
+ 'data-[readonly=true]:data-[checked=false]:border-content-tertiary',
48
+ 'data-[readonly=true]:data-[checked=true]:border-content-tertiary',
49
+ 'data-[readonly=true]:data-[invalid=true]:border-stroke-danger',
50
+ 'data-[readonly=true]:data-[hover=true]:data-[checked=false]:bg-transparent',
51
+ 'data-[readonly=true]:data-[hover=true]:data-[invalid=true]:data-[checked=false]:bg-transparent',
52
+ 'data-[readonly=true]:data-[hover=true]:data-[checked=false]:border-content-tertiary',
53
+ 'data-[readonly=true]:data-[hover=true]:data-[checked=true]:border-content-tertiary',
54
+ 'data-[readonly=true]:data-[hover=true]:data-[invalid=true]:border-stroke-danger',
55
+ 'web:active:data-[readonly=true]:data-[hover=true]:data-[checked=false]:border-content-tertiary',
56
+ 'web:active:data-[readonly=true]:data-[hover=true]:data-[checked=true]:border-content-tertiary',
57
+ 'web:active:data-[readonly=true]:data-[hover=true]:data-[invalid=true]:data-[checked=false]:border-stroke-danger',
58
+ 'web:active:data-[readonly=true]:data-[hover=true]:data-[checked=true]:data-[invalid=true]:border-stroke-danger',
41
59
  ].join(' '),
42
60
  default: [
43
61
  // Native: Uniwind only evaluates the last data-[] condition in a compound chain.
44
62
  // Use single-condition data-[field-state=...] selectors (emitted by RadioIndicator).
63
+ 'data-[field-state=readonly-unchecked]:border-content-tertiary',
64
+ 'data-[field-state=readonly-checked]:border-content-tertiary',
65
+ 'data-[field-state=readonly-unchecked-invalid]:border-stroke-danger',
66
+ 'data-[field-state=readonly-checked-invalid]:border-stroke-danger',
45
67
  'data-[field-state=active]:border-stroke-hover',
46
68
  'data-[field-state=invalid-active]:border-surface-danger-strong-active',
47
69
  'data-[field-state=checked-active]:border-surface-action-strong-active',
@@ -52,9 +74,12 @@ export const radioIndicatorVariants = cva([
52
74
 
53
75
  export const radioInnerDotVariants = cva([
54
76
  'rounded-[var(--border-radius-round)]',
77
+ 'overflow-hidden',
55
78
  'h-2.5 w-2.5',
56
- 'scale-0',
57
- 'data-[checked=true]:scale-100',
79
+ // Opacity (not scale): iOS compositing of scale-0 views samples the parent ring, so the
80
+ // checked fill never paints and the dot looks content-tertiary instead of content-primary.
81
+ 'opacity-0',
82
+ 'data-[checked=true]:opacity-100',
58
83
  'bg-surface-action-strong',
59
84
  'data-[invalid=true]:bg-surface-danger-strong',
60
85
  Platform.select({
@@ -64,15 +89,30 @@ export const radioInnerDotVariants = cva([
64
89
  // Active via group-active on indicator (inner dot is not the :active target).
65
90
  'web:group-active:data-[hover=true]:data-[checked=true]:bg-surface-action-strong-active',
66
91
  'web:group-active:data-[hover=true]:data-[checked=true]:data-[invalid=true]:bg-surface-danger-strong-active',
92
+ // Read-only checked — Figma: content-primary dot; invalid retains danger.
93
+ 'data-[readonly=true]:data-[checked=true]:bg-content-primary',
94
+ 'data-[readonly=true]:data-[checked=true]:data-[invalid=true]:bg-surface-danger-strong',
95
+ 'web:group-active:data-[readonly=true]:data-[hover=true]:data-[checked=true]:bg-content-primary',
96
+ 'web:group-active:data-[readonly=true]:data-[hover=true]:data-[checked=true]:data-[invalid=true]:bg-surface-danger-strong',
67
97
  ].join(' '),
68
98
  default: [
69
99
  'data-[field-state=checked-active]:bg-surface-action-strong-active',
70
100
  'data-[field-state=checked-invalid-active]:bg-surface-danger-strong-active',
101
+ // `!` beats the base `bg-surface-action-strong` — Uniwind native does not cascade
102
+ // data-attribute backgrounds over an unconditional bg on the same View.
103
+ 'data-[readonly=true]:bg-content-primary!',
104
+ 'data-[field-state=readonly-checked]:bg-content-primary!',
105
+ 'data-[field-state=readonly-checked-invalid]:bg-surface-danger-strong!',
71
106
  ].join(' '),
72
107
  }),
73
108
  ]);
74
109
 
75
- export const radioLabelVariants = cva(['body-md', 'text-content-primary']);
110
+ export const radioLabelVariants = cva([
111
+ 'body-md',
112
+ 'text-content-primary',
113
+ 'data-[disabled=true]:text-content-tertiary',
114
+ 'data-[readonly=true]:cursor-default',
115
+ ]);
76
116
 
77
117
  export const radioGroupVariants = cva([], {
78
118
  variants: {
@@ -1,5 +1,5 @@
1
1
  import { cva, type VariantProps } from '@cdx-ui/utils';
2
- import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
2
+ import { DISABLED_CURSOR, TRANSITION_COLORS, WEB_NARROW_FIELD_TEXT } from '../../styles/constants';
3
3
 
4
4
  // ── Trigger ─────────────────────────────────────────────────
5
5
 
@@ -46,6 +46,9 @@ export const selectTriggerVariants = cva(
46
46
  size: {
47
47
  default: [
48
48
  'h-11 min-h-11 px-4 py-3 gap-4',
49
+ // Web <600px: trim vertical padding so 16px / leading-normal (24px line box) fits
50
+ // in h-11. Native and web ≥600px keep py-3 at 14px text (Figma Viewport=Wide).
51
+ 'web:max-[600px]:py-2',
49
52
  // Native only: subtract one hairline so the wider focus border doesn't shift layout.
50
53
  'native:data-[focus=true]:px-[calc(var(--spacing-4)-var(--spacing-px))]',
51
54
  'native:data-[focus=true]:py-[calc(var(--spacing-3)-var(--spacing-px))]',
@@ -54,6 +57,9 @@ export const selectTriggerVariants = cva(
54
57
  ],
55
58
  small: [
56
59
  'h-8 min-h-8 px-3 py-2 gap-1.5',
60
+ // Web <600px: grow shell to h-9 so 16px text fits. Native and web ≥600px stay
61
+ // h-8 at 12px text.
62
+ 'web:max-[600px]:h-9 web:max-[600px]:min-h-9 web:max-[600px]:py-1.5',
57
63
  'native:data-[focus=true]:px-[calc(var(--spacing-3)-var(--spacing-px))]',
58
64
  'native:data-[focus=true]:py-[calc(var(--spacing-2)-var(--spacing-px))]',
59
65
  'native:data-[state=open]:px-[calc(var(--spacing-3)-var(--spacing-px))]',
@@ -80,8 +86,10 @@ export const selectValueVariants = cva(
80
86
  {
81
87
  variants: {
82
88
  size: {
83
- default: 'text-[0.875rem]',
84
- small: 'text-[0.75rem]',
89
+ // Web <600px: 16px matches Input/Textarea and prevents iOS Safari focus-zoom.
90
+ // Native and web ≥600px keep 14px / 12px (Figma Viewport=Wide).
91
+ default: ['text-[0.875rem]', WEB_NARROW_FIELD_TEXT],
92
+ small: ['text-[0.75rem]', WEB_NARROW_FIELD_TEXT],
85
93
  },
86
94
  },
87
95
  defaultVariants: {
@@ -4,40 +4,40 @@ import { cva, type VariantProps } from '@cdx-ui/utils';
4
4
  // NATIVE — Uniwind className bindings for RN Switch
5
5
  // =============================================================================
6
6
 
7
- export const switchThumbColorVariants = cva(['accent-white'], {
7
+ export const switchThumbColorVariants = cva(['accent-surface-primary'], {
8
8
  variants: {
9
9
  isDisabled: {
10
- true: 'accent-gray-200',
10
+ true: 'accent-surface-neutral-subtle',
11
11
  false: '',
12
12
  },
13
13
  },
14
14
  defaultVariants: { isDisabled: false },
15
15
  });
16
16
 
17
- export const switchTrackOnVariants = cva(['accent-blue-500'], {
17
+ export const switchTrackOnVariants = cva(['accent-surface-action-strong'], {
18
18
  variants: {
19
19
  isInvalid: {
20
- true: 'accent-red-500',
20
+ true: 'accent-surface-danger-strong',
21
21
  false: '',
22
22
  },
23
23
  },
24
24
  defaultVariants: { isInvalid: false },
25
25
  });
26
26
 
27
- export const switchTrackOffVariants = cva(['accent-gray-300'], {
27
+ export const switchTrackOffVariants = cva(['accent-surface-neutral-subtle'], {
28
28
  variants: {
29
29
  isInvalid: {
30
- true: 'accent-red-300',
30
+ true: 'accent-surface-danger-subtle',
31
31
  false: '',
32
32
  },
33
33
  },
34
34
  defaultVariants: { isInvalid: false },
35
35
  });
36
36
 
37
- export const switchIosBgVariants = cva(['accent-gray-300'], {
37
+ export const switchIosBgVariants = cva(['accent-surface-neutral-subtle'], {
38
38
  variants: {
39
39
  isInvalid: {
40
- true: 'accent-red-300',
40
+ true: 'accent-surface-danger-subtle',
41
41
  false: '',
42
42
  },
43
43
  },
@@ -53,8 +53,8 @@ export const switchTrackVariants = cva(
53
53
  'flex-row items-center rounded-full cursor-pointer',
54
54
  'transition-colors duration-200',
55
55
  'data-[disabled=true]:opacity-50 data-[disabled=true]:cursor-not-allowed',
56
- 'web:data-[focus-visible=true]:ring-3 web:data-[focus-visible=true]:ring-blue-300/50',
57
- 'web:data-[invalid=true]:data-[focus-visible=true]:ring-red-300/50',
56
+ 'web:data-[focus-visible=true]:ring-3 web:data-[focus-visible=true]:ring-stroke-focus/50',
57
+ 'web:data-[invalid=true]:data-[focus-visible=true]:ring-stroke-danger/50',
58
58
  ],
59
59
  {
60
60
  variants: {
@@ -73,8 +73,8 @@ export const switchTrackVariants = cva(
73
73
  export const switchWebTrackColorVariants = cva([], {
74
74
  variants: {
75
75
  isChecked: {
76
- true: 'bg-blue-500 data-[hover=true]:bg-blue-600',
77
- false: 'bg-gray-300 data-[hover=true]:bg-gray-400',
76
+ true: 'bg-surface-action-strong data-[hover=true]:bg-surface-action-strong-hover',
77
+ false: 'bg-surface-neutral-subtle data-[hover=true]:bg-surface-neutral-subtle-hover',
78
78
  },
79
79
  isInvalid: {
80
80
  true: '',
@@ -82,8 +82,16 @@ export const switchWebTrackColorVariants = cva([], {
82
82
  },
83
83
  },
84
84
  compoundVariants: [
85
- { isChecked: true, isInvalid: true, className: 'bg-red-500 data-[hover=true]:bg-red-600' },
86
- { isChecked: false, isInvalid: true, className: 'bg-red-300 data-[hover=true]:bg-red-400' },
85
+ {
86
+ isChecked: true,
87
+ isInvalid: true,
88
+ className: 'bg-surface-danger-strong data-[hover=true]:bg-surface-danger-strong-hover',
89
+ },
90
+ {
91
+ isChecked: false,
92
+ isInvalid: true,
93
+ className: 'bg-surface-danger-subtle data-[hover=true]:bg-surface-danger-subtle-hover',
94
+ },
87
95
  ],
88
96
  defaultVariants: {
89
97
  isChecked: false,
@@ -92,7 +100,7 @@ export const switchWebTrackColorVariants = cva([], {
92
100
  });
93
101
 
94
102
  export const switchThumbVariants = cva(
95
- ['rounded-full bg-white shadow-sm', 'transition-transform duration-200'],
103
+ ['rounded-full bg-surface-primary shadow-sm', 'transition-transform duration-200'],
96
104
  {
97
105
  variants: {
98
106
  size: {
@@ -1,5 +1,5 @@
1
1
  import { cva, type VariantProps } from '@cdx-ui/utils';
2
- import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
2
+ import { DISABLED_CURSOR, TRANSITION_COLORS, WEB_NARROW_FIELD_TEXT } from '../../styles/constants';
3
3
 
4
4
  export const textareaRootVariants = cva(
5
5
  [
@@ -71,8 +71,10 @@ export const textareaFieldVariants = cva(
71
71
  {
72
72
  variants: {
73
73
  size: {
74
- default: 'text-[0.875rem]',
75
- small: 'text-[0.75rem]',
74
+ // Web <600px: 16px prevents iOS Safari focus-zoom. Native and web ≥600px keep
75
+ // 14px / 12px (Figma Viewport=Wide).
76
+ default: ['text-[0.875rem]', WEB_NARROW_FIELD_TEXT],
77
+ small: ['text-[0.75rem]', WEB_NARROW_FIELD_TEXT],
76
78
  },
77
79
  },
78
80
  defaultVariants: {
@@ -8,3 +8,7 @@ export const TRANSITION_COLORS = 'transition-colors duration-150';
8
8
  export const DISABLED_OPACITY = 'data-[disabled=true]:opacity-50';
9
9
  export const DISABLED_CURSOR =
10
10
  'data-[disabled=true]:cursor-not-allowed data-[disabled=true]:pointer-events-none';
11
+
12
+ // Web-only iOS Safari focus-zoom guard. Figma Viewport=Narrow (<600px) uses body/lg
13
+ // (16px / 24px); Viewport=Wide (≥600px) keeps the size variant's original px size.
14
+ export const WEB_NARROW_FIELD_TEXT = 'web:max-[600px]:text-base web:max-[600px]:leading-normal';