@toptal/picasso-switch 100.0.0 → 101.0.0

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.
@@ -10,6 +10,11 @@ export interface Props extends BaseProps, TextLabelProps, Omit<ButtonHTMLAttribu
10
10
  label?: ReactNode;
11
11
  /** Callback invoked when `Switch` changed its value */
12
12
  onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => void;
13
+ /** Testids of the `Switch` internal elements. The top-level `data-testid` stays on the `[role="switch"]` element */
14
+ testIds?: {
15
+ /** Testid of the visually hidden native `<input>` (rendered as a sibling of the `[role="switch"]` element) */
16
+ input?: string;
17
+ };
13
18
  }
14
19
  export declare const Switch: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLButtonElement>>;
15
20
  export default Switch;
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../src/Switch/Switch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC5D,OAAO,KAAqB,MAAM,OAAO,CAAA;AAIzC,MAAM,WAAW,KACf,SAAQ,SAAS,EACf,cAAc,EACd,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpE,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAC1C,OAAO,EAAE,OAAO,KACb,IAAI,CAAA;CACV;AAED,eAAO,MAAM,MAAM,iFAmGjB,CAAA;AAIF,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"Switch.d.ts","sourceRoot":"","sources":["../../../src/Switch/Switch.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC5D,OAAO,KAAqB,MAAM,OAAO,CAAA;AAIzC,MAAM,WAAW,KACf,SAAQ,SAAS,EACf,cAAc,EACd,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpE,uCAAuC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kCAAkC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAC1C,OAAO,EAAE,OAAO,KACb,IAAI,CAAA;IACT,oHAAoH;IACpH,OAAO,CAAC,EAAE;QACR,8GAA8G;QAC9G,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,eAAO,MAAM,MAAM,iFAsGjB,CAAA;AAIF,eAAe,MAAM,CAAA"}
@@ -11,22 +11,25 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { Switch as BaseUISwitch } from '@base-ui/react/switch';
13
13
  import { useBaseUiId } from '@base-ui/react/internals/useBaseUiId';
14
- import { toReactChangeEvent } from '@toptal/picasso-shared';
14
+ import { toReactChangeEvent, useInputTestId } from '@toptal/picasso-shared';
15
15
  import React, { forwardRef } from 'react';
16
16
  import { FormControlLabel } from '@toptal/picasso-form-label';
17
17
  import { twMerge } from '@toptal/picasso-tailwind-merge';
18
18
  export const Switch = forwardRef(function Switch(props, ref) {
19
19
  var _a;
20
20
  const { onChange, disabled = false, label, id, className, style, checked, titleCase, value, color: _color, // eslint-disable-line @typescript-eslint/no-unused-vars
21
- 'data-testid': dataTestId } = props, rest = __rest(props, ["onChange", "disabled", "label", "id", "className", "style", "checked", "titleCase", "value", "color", 'data-testid']);
21
+ testIds } = props, rest = __rest(props, ["onChange", "disabled", "label", "id", "className", "style", "checked", "titleCase", "value", "color", "testIds"]);
22
22
  const handleCheckedChange = (nextChecked, { event }) => onChange === null || onChange === void 0 ? void 0 : onChange(toReactChangeEvent(event), nextChecked);
23
23
  const rootRest = rest;
24
24
  // Name the control via `aria-labelledby` so it is the single label-associated
25
25
  // node (see FormControlLabel `labelId`), only when there is a label.
26
26
  const generatedLabelId = useBaseUiId();
27
27
  const labelId = label ? generatedLabelId : undefined;
28
+ // Base UI renders the hidden input beside the root and exposes it only
29
+ // through `inputRef`, so the testid is stamped on it imperatively
30
+ const inputTestIdRef = useInputTestId(testIds === null || testIds === void 0 ? void 0 : testIds.input);
28
31
  const switchElement = (React.createElement("span", { className: 'relative inline-flex shrink-0 align-middle overflow-clip [overflow-clip-margin:6px]' },
29
- React.createElement(BaseUISwitch.Root, Object.assign({}, rootRest, { ref: ref, "aria-labelledby": (_a = rootRest['aria-labelledby']) !== null && _a !== void 0 ? _a : labelId, checked: checked, className: twMerge('w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group', 'cursor-pointer outline-none data-[disabled]:cursor-default', className), style: style, disabled: disabled, id: id, value: value === undefined ? undefined : String(value), onCheckedChange: handleCheckedChange, "data-testid": label ? undefined : dataTestId }),
32
+ React.createElement(BaseUISwitch.Root, Object.assign({}, rootRest, { ref: ref, inputRef: inputTestIdRef, "aria-labelledby": (_a = rootRest['aria-labelledby']) !== null && _a !== void 0 ? _a : labelId, checked: checked, className: twMerge('w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group', 'cursor-pointer outline-none data-[disabled]:cursor-default', className), style: style, disabled: disabled, id: id, value: value === undefined ? undefined : String(value), onCheckedChange: handleCheckedChange }),
30
33
  React.createElement("span", { className: twMerge('w-full h-full border border-solid bg-gray-600 border-gray-600 opacity-100 rounded-[12px]', 'transition-colors duration-300 ease-out', 'group-data-[checked]:bg-blue-500 group-data-[checked]:border-blue-500', 'group-data-[disabled]:opacity-40', 'group-[[data-disabled][data-unchecked]]:bg-black') }),
31
34
  React.createElement(BaseUISwitch.Thumb, { className: twMerge('w-[22px] h-[22px] bg-current text-white block rounded-full shadow-1 absolute z-10 p-0 top-[1px] left-[1px]', 'transition-transform duration-150 ease-out', 'group-[:hover:not([data-disabled])]:shadow-[0_0_0_4px_rgba(32,78,207,0.48)]', 'group-focus-visible:shadow-[0_0_0_4px_rgba(32,78,207,0.48)]', 'group-data-[checked]:translate-x-[16px]') }))));
32
35
  if (!label) {
@@ -35,7 +38,7 @@ export const Switch = forwardRef(function Switch(props, ref) {
35
38
  return (React.createElement(FormControlLabel, { classes: {
36
39
  root: 'items-start text-lg',
37
40
  label: 'ml-[0.5em] mt-[0.25em] max-w-[calc(100%-1em-0.5em+1px)]',
38
- }, control: switchElement, labelId: labelId, disabled: disabled, label: label, titleCase: titleCase, "data-testid": dataTestId }));
41
+ }, control: switchElement, labelId: labelId, disabled: disabled, label: label, titleCase: titleCase }));
39
42
  });
40
43
  Switch.displayName = 'Switch';
41
44
  export default Switch;
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../../src/Switch/Switch.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAA;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAE3D,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAmBxD,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAA2B,SAAS,MAAM,CACxE,KAAK,EACL,GAAG;;IAEH,MAAM,EACJ,QAAQ,EACR,QAAQ,GAAG,KAAK,EAChB,KAAK,EACL,EAAE,EACF,SAAS,EACT,KAAK,EACL,OAAO,EACP,SAAS,EACT,KAAK,EACL,KAAK,EAAE,MAAM,EAAE,wDAAwD;IACvE,aAAa,EAAE,UAAU,KAEvB,KAAK,EADJ,IAAI,UACL,KAAK,EAbH,sHAaL,CAAQ,CAAA;IAET,MAAM,mBAAmB,GAA+C,CACtE,WAAW,EACX,EAAE,KAAK,EAAE,EACT,EAAE,CAAC,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,kBAAkB,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAA;IAEvD,MAAM,QAAQ,GAAG,IAShB,CAAA;IAED,8EAA8E;IAC9E,qEAAqE;IACrE,MAAM,gBAAgB,GAAG,WAAW,EAAE,CAAA;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAA;IAEpD,MAAM,aAAa,GAAG,CACpB,8BAAM,SAAS,EAAC,qFAAqF;QACnG,oBAAC,YAAY,CAAC,IAAI,oBACZ,QAAQ,IACZ,GAAG,EAAE,GAAG,qBACS,MAAA,QAAQ,CAAC,iBAAiB,CAAC,mCAAI,OAAO,EACvD,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,OAAO,CAChB,6FAA6F,EAC7F,4DAA4D,EAC5D,SAAS,CACV,EACD,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACtD,eAAe,EAAE,mBAAmB,iBACvB,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;YAE3C,8BACE,SAAS,EAAE,OAAO,CAChB,0FAA0F,EAC1F,yCAAyC,EACzC,uEAAuE,EACvE,kCAAkC,EAClC,kDAAkD,CACnD,GACD;YACF,oBAAC,YAAY,CAAC,KAAK,IACjB,SAAS,EAAE,OAAO,CAChB,4GAA4G,EAC5G,4CAA4C,EAC5C,6EAA6E,EAC7E,6DAA6D,EAC7D,yCAAyC,CAC1C,GACD,CACgB,CACf,CACR,CAAA;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,aAAa,CAAA;IACtB,CAAC;IAED,OAAO,CACL,oBAAC,gBAAgB,IACf,OAAO,EAAE;YACP,IAAI,EAAE,qBAAqB;YAC3B,KAAK,EAAE,yDAAyD;SACjE,EACD,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,SAAS,iBACP,UAAU,GACvB,CACH,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAA;AAE7B,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"Switch.js","sourceRoot":"","sources":["../../../src/Switch/Switch.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/picasso-switch",
3
- "version": "100.0.0",
3
+ "version": "101.0.0",
4
4
  "description": "Toptal UI components library - Switch",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,8 +19,8 @@
19
19
  "homepage": "https://github.com/toptal/picasso/tree/master/packages/picasso#readme",
20
20
  "dependencies": {
21
21
  "@base-ui/react": "^1.6.0",
22
- "@toptal/picasso-form-label": "100.0.0",
23
- "@toptal/picasso-shared": "100.0.0"
22
+ "@toptal/picasso-form-label": "100.0.1",
23
+ "@toptal/picasso-shared": "100.1.0"
24
24
  },
25
25
  "sideEffects": [
26
26
  "**/styles.ts",
@@ -36,9 +36,9 @@
36
36
  ".": "./dist-package/src/index.js"
37
37
  },
38
38
  "devDependencies": {
39
+ "@toptal/picasso-tailwind-merge": "100.0.0",
39
40
  "@toptal/picasso-provider": "100.0.0",
40
- "@toptal/picasso-test-utils": "100.0.0",
41
- "@toptal/picasso-tailwind-merge": "100.0.0"
41
+ "@toptal/picasso-test-utils": "100.0.1"
42
42
  },
43
43
  "files": [
44
44
  "dist-package/**",
@@ -1,7 +1,7 @@
1
1
  import { Switch as BaseUISwitch } from '@base-ui/react/switch'
2
2
  import { useBaseUiId } from '@base-ui/react/internals/useBaseUiId'
3
3
  import type { BaseProps, TextLabelProps } from '@toptal/picasso-shared'
4
- import { toReactChangeEvent } from '@toptal/picasso-shared'
4
+ import { toReactChangeEvent, useInputTestId } from '@toptal/picasso-shared'
5
5
  import type { ButtonHTMLAttributes, ReactNode } from 'react'
6
6
  import React, { forwardRef } from 'react'
7
7
  import { FormControlLabel } from '@toptal/picasso-form-label'
@@ -22,6 +22,11 @@ export interface Props
22
22
  event: React.ChangeEvent<HTMLInputElement>,
23
23
  checked: boolean
24
24
  ) => void
25
+ /** Testids of the `Switch` internal elements. The top-level `data-testid` stays on the `[role="switch"]` element */
26
+ testIds?: {
27
+ /** Testid of the visually hidden native `<input>` (rendered as a sibling of the `[role="switch"]` element) */
28
+ input?: string
29
+ }
25
30
  }
26
31
 
27
32
  export const Switch = forwardRef<HTMLButtonElement, Props>(function Switch(
@@ -39,7 +44,7 @@ export const Switch = forwardRef<HTMLButtonElement, Props>(function Switch(
39
44
  titleCase,
40
45
  value,
41
46
  color: _color, // eslint-disable-line @typescript-eslint/no-unused-vars
42
- 'data-testid': dataTestId,
47
+ testIds,
43
48
  ...rest
44
49
  } = props
45
50
 
@@ -64,11 +69,16 @@ export const Switch = forwardRef<HTMLButtonElement, Props>(function Switch(
64
69
  const generatedLabelId = useBaseUiId()
65
70
  const labelId = label ? generatedLabelId : undefined
66
71
 
72
+ // Base UI renders the hidden input beside the root and exposes it only
73
+ // through `inputRef`, so the testid is stamped on it imperatively
74
+ const inputTestIdRef = useInputTestId(testIds?.input)
75
+
67
76
  const switchElement = (
68
77
  <span className='relative inline-flex shrink-0 align-middle overflow-clip [overflow-clip-margin:6px]'>
69
78
  <BaseUISwitch.Root
70
79
  {...rootRest}
71
80
  ref={ref}
81
+ inputRef={inputTestIdRef}
72
82
  aria-labelledby={rootRest['aria-labelledby'] ?? labelId}
73
83
  checked={checked}
74
84
  className={twMerge(
@@ -81,7 +91,6 @@ export const Switch = forwardRef<HTMLButtonElement, Props>(function Switch(
81
91
  id={id}
82
92
  value={value === undefined ? undefined : String(value)}
83
93
  onCheckedChange={handleCheckedChange}
84
- data-testid={label ? undefined : dataTestId}
85
94
  >
86
95
  <span
87
96
  className={twMerge(
@@ -120,7 +129,6 @@ export const Switch = forwardRef<HTMLButtonElement, Props>(function Switch(
120
129
  disabled={disabled}
121
130
  label={label}
122
131
  titleCase={titleCase}
123
- data-testid={dataTestId}
124
132
  />
125
133
  )
126
134
  })
@@ -1,49 +1,24 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`Switch behaves correctly when interacting 1`] = `
4
- <div
5
- class="inline-flex max-w align-middle -webkit-tap-highlight-color mx-0 cursor-pointer items-start text-lg"
4
+ <span
5
+ aria-checked="true"
6
+ aria-labelledby="base-ui-:rk:"
7
+ class="w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group cursor-pointer outline-none data-[disabled]:cursor-default"
8
+ data-checked=""
6
9
  data-testid="switch"
10
+ id="base-ui-:rl:"
11
+ role="switch"
12
+ tabindex="0"
7
13
  >
8
14
  <span
9
- class="relative inline-flex shrink-0 align-middle overflow-clip [overflow-clip"
10
- >
11
- <span
12
- aria-checked="true"
13
- aria-labelledby="base-ui-:rk:"
14
- class="w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group cursor-pointer outline-none data-[disabled]:cursor-default"
15
- data-checked=""
16
- id="base-ui-:rl:"
17
- role="switch"
18
- tabindex="0"
19
- >
20
- <span
21
- class="w-full h-full border border-solid bg-gray border-gray opacity-100 rounded-[12px] transition-colors duration-300 ease-out group-data-[checked]:bg-blue group-data-[checked]:border-blue group-data-[disabled]:opacity group-[[data-disabled][data-unchecked]]:bg-black"
22
- />
23
- <span
24
- class="w-[22px] h-[22px] bg-current text-white block rounded-full shadow-1 absolute z-10 p-0 top-[1px] left-[1px] transition-transform duration-150 ease-out group-[:hover:not([data-disabled])]:shadow-[0_0_0_4px_rgba(32,78,207,0.48)] group-focus-visible:shadow group-data-[checked]:translate-x"
25
- data-checked=""
26
- />
27
- </span>
28
- <input
29
- aria-hidden="true"
30
- id="base-ui-:rm:"
31
- style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;"
32
- tabindex="-1"
33
- type="checkbox"
34
- />
35
- </span>
15
+ class="w-full h-full border border-solid bg-gray border-gray opacity-100 rounded-[12px] transition-colors duration-300 ease-out group-data-[checked]:bg-blue group-data-[checked]:border-blue group-data-[disabled]:opacity group-[[data-disabled][data-unchecked]]:bg-black"
16
+ />
36
17
  <span
37
- class="inline-block leading-[1em] mb-0 text-graphite ml-[0.5em] mt-[0.25em] max-w-[calc(100%-1em"
38
- id="base-ui-:rk:"
39
- >
40
- <span
41
- class="align-top text-[0.8125rem]"
42
- >
43
- Switch
44
- </span>
45
- </span>
46
- </div>
18
+ class="w-[22px] h-[22px] bg-current text-white block rounded-full shadow-1 absolute z-10 p-0 top-[1px] left-[1px] transition-transform duration-150 ease-out group-[:hover:not([data-disabled])]:shadow-[0_0_0_4px_rgba(32,78,207,0.48)] group-focus-visible:shadow group-data-[checked]:translate-x"
19
+ data-checked=""
20
+ />
21
+ </span>
47
22
  `;
48
23
 
49
24
  exports[`Switch renders default Switch without label 1`] = `
@@ -84,52 +59,58 @@ exports[`Switch renders default Switch without label 1`] = `
84
59
  `;
85
60
 
86
61
  exports[`Switch renders disabled state 1`] = `
87
- <div
88
- class="inline-flex max-w align-middle -webkit-tap-highlight-color mx-0 cursor-default items-start text-lg"
89
- data-testid="switch"
90
- >
91
- <span
92
- class="relative inline-flex shrink-0 align-middle overflow-clip [overflow-clip"
93
- disabled=""
62
+ <div>
63
+ <div
64
+ class="flex-1 box-border [:where(&)_*]:font-sans [&_*]:[box-sizing:inherit] [&_*::before]:[box-sizing:inherit] [&_*::after]:[box-sizing:inherit]"
94
65
  >
95
- <span
96
- aria-checked="false"
97
- aria-disabled="true"
98
- aria-labelledby="base-ui-:r8:"
99
- class="w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group cursor-pointer outline-none data-[disabled]:cursor-default"
100
- data-disabled=""
101
- data-unchecked=""
102
- id="base-ui-:r9:"
103
- role="switch"
104
- tabindex="-1"
66
+ <div
67
+ class="inline-flex max-w align-middle -webkit-tap-highlight-color mx-0 cursor-default items-start text-lg"
105
68
  >
106
69
  <span
107
- class="w-full h-full border border-solid bg-gray border-gray opacity-100 rounded-[12px] transition-colors duration-300 ease-out group-data-[checked]:bg-blue group-data-[checked]:border-blue group-data-[disabled]:opacity group-[[data-disabled][data-unchecked]]:bg-black"
108
- />
70
+ class="relative inline-flex shrink-0 align-middle overflow-clip [overflow-clip"
71
+ disabled=""
72
+ >
73
+ <span
74
+ aria-checked="false"
75
+ aria-disabled="true"
76
+ aria-labelledby="base-ui-:r8:"
77
+ class="w-[40px] h-[24px] p-0 relative inline-flex z-0 overflow-visible shrink-0 align-middle group cursor-pointer outline-none data-[disabled]:cursor-default"
78
+ data-disabled=""
79
+ data-testid="switch"
80
+ data-unchecked=""
81
+ id="base-ui-:r9:"
82
+ role="switch"
83
+ tabindex="-1"
84
+ >
85
+ <span
86
+ class="w-full h-full border border-solid bg-gray border-gray opacity-100 rounded-[12px] transition-colors duration-300 ease-out group-data-[checked]:bg-blue group-data-[checked]:border-blue group-data-[disabled]:opacity group-[[data-disabled][data-unchecked]]:bg-black"
87
+ />
88
+ <span
89
+ class="w-[22px] h-[22px] bg-current text-white block rounded-full shadow-1 absolute z-10 p-0 top-[1px] left-[1px] transition-transform duration-150 ease-out group-[:hover:not([data-disabled])]:shadow-[0_0_0_4px_rgba(32,78,207,0.48)] group-focus-visible:shadow group-data-[checked]:translate-x"
90
+ data-disabled=""
91
+ data-unchecked=""
92
+ />
93
+ </span>
94
+ <input
95
+ aria-hidden="true"
96
+ disabled=""
97
+ id="base-ui-:ra:"
98
+ style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;"
99
+ tabindex="-1"
100
+ type="checkbox"
101
+ />
102
+ </span>
109
103
  <span
110
- class="w-[22px] h-[22px] bg-current text-white block rounded-full shadow-1 absolute z-10 p-0 top-[1px] left-[1px] transition-transform duration-150 ease-out group-[:hover:not([data-disabled])]:shadow-[0_0_0_4px_rgba(32,78,207,0.48)] group-focus-visible:shadow group-data-[checked]:translate-x"
111
- data-disabled=""
112
- data-unchecked=""
113
- />
114
- </span>
115
- <input
116
- aria-hidden="true"
117
- disabled=""
118
- id="base-ui-:ra:"
119
- style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;"
120
- tabindex="-1"
121
- type="checkbox"
122
- />
123
- </span>
124
- <span
125
- class="inline-block leading-[1em] mb-0 text-graphite pointer-events ml-[0.5em] mt-[0.25em] max-w-[calc(100%-1em"
126
- id="base-ui-:r8:"
127
- >
128
- <span
129
- class="align-top text-[0.8125rem]"
130
- >
131
- Disabled
132
- </span>
133
- </span>
104
+ class="inline-block leading-[1em] mb-0 text-graphite pointer-events ml-[0.5em] mt-[0.25em] max-w-[calc(100%-1em"
105
+ id="base-ui-:r8:"
106
+ >
107
+ <span
108
+ class="align-top text-[0.8125rem]"
109
+ >
110
+ Disabled
111
+ </span>
112
+ </span>
113
+ </div>
114
+ </div>
134
115
  </div>
135
116
  `;
@@ -33,36 +33,36 @@ describe('Switch', () => {
33
33
  })
34
34
 
35
35
  it('renders default Switch with label', () => {
36
- const { getByTestId } = renderSwitch({ label: 'A Switch' })
36
+ const { getByRole } = renderSwitch({ label: 'A Switch' })
37
37
 
38
- expect(getByTestId('switch')).toHaveTextContent('A Switch')
38
+ expect(getByRole('switch', { name: 'A Switch' })).toBeVisible()
39
39
  })
40
40
 
41
41
  it('renders disabled state', () => {
42
- const { getByTestId } = renderSwitch({
42
+ const { container } = renderSwitch({
43
43
  disabled: true,
44
44
  label: 'Disabled',
45
45
  })
46
46
 
47
- expect(getByTestId('switch')).toMatchSnapshot()
47
+ expect(container).toMatchSnapshot()
48
48
  })
49
49
 
50
50
  it('transforms text to title case when Picasso titleCase property is true', () => {
51
- const { getByTestId } = renderSwitch(
51
+ const { getByText } = renderSwitch(
52
52
  { label: 'abc ac4' },
53
53
  { titleCase: true }
54
54
  )
55
55
 
56
- expect(getByTestId('switch')).toHaveTextContent('Abc Ac4')
56
+ expect(getByText('Abc Ac4')).toBeVisible()
57
57
  })
58
58
 
59
59
  it('transforms text to title case when Picasso titleCase property is true but the component property overrides it', () => {
60
- const { getByTestId } = renderSwitch(
60
+ const { getByText } = renderSwitch(
61
61
  { label: 'abc dp3', titleCase: false },
62
62
  { titleCase: true }
63
63
  )
64
64
 
65
- expect(getByTestId('switch')).toHaveTextContent('abc dp3')
65
+ expect(getByText('abc dp3')).toBeVisible()
66
66
  })
67
67
 
68
68
  it('behaves correctly when interacting', () => {
@@ -123,4 +123,34 @@ describe('Switch', () => {
123
123
  })
124
124
  })
125
125
  })
126
+
127
+ describe('testIds', () => {
128
+ it('keeps the top-level data-testid on the role element, with and without label', () => {
129
+ const labelled = render(<Switch data-testid='switch' label='A Switch' />)
130
+
131
+ expect(labelled.getByTestId('switch')).toBe(labelled.getByRole('switch'))
132
+
133
+ labelled.unmount()
134
+
135
+ const bare = render(<Switch data-testid='switch' />)
136
+
137
+ expect(bare.getByTestId('switch')).toBe(bare.getByRole('switch'))
138
+ })
139
+
140
+ it('places testIds.input on the visually hidden native input', () => {
141
+ const { getByTestId, getByRole } = render(
142
+ <Switch label='A Switch' testIds={{ input: 'switch-input' }} />
143
+ )
144
+
145
+ const input = getByTestId('switch-input')
146
+
147
+ expect(input).toBeInstanceOf(HTMLInputElement)
148
+ // the input is rendered beside the role element, not inside it
149
+ expect(getByRole('switch')).not.toContainElement(input)
150
+
151
+ fireEvent.click(getByRole('switch'))
152
+
153
+ expect(input).toBeChecked()
154
+ })
155
+ })
126
156
  })