@dss-kit/ui-kit 1.0.3 → 1.0.4

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.
@@ -38,11 +38,15 @@ interface SwitchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>,
38
38
  */
39
39
  variant?: SwitchVariant;
40
40
  /**
41
- * Заголовок переключателя.
41
+ * Заголовок переключателя — его доступное имя.
42
+ * Рендерится внутри `<label>`, поэтому `ReactNode` должен быть строчным
43
+ * контентом (без `div`, `p` и интерактивных элементов).
44
+ * Без `title` и `text` передайте `aria-label`.
42
45
  */
43
46
  title?: string | React.ReactNode;
44
47
  /**
45
- * Текст переключателя.
48
+ * Текст переключателя. Вместе с `title` связывается с переключателем
49
+ * через `aria-describedby`. Ограничения на `ReactNode` те же, что у `title`.
46
50
  */
47
51
  text?: string | React.ReactNode;
48
52
  /**
@@ -73,13 +77,13 @@ interface SwitchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>,
73
77
  className?: string;
74
78
  /** Класс для внешнего `Box`-обёртки над `<label>`. */
75
79
  wrapperClassName?: string;
76
- /** Класс для контейнера переключателя (`Box` со `switchContainer`). */
80
+ /** Класс для контейнера переключателя (`<span>` со `switchContainer`). */
77
81
  switchContainerClassName?: string;
78
- /** Класс для рельса-трека переключателя (`<div>` с `switch`). */
82
+ /** Класс для рельса-трека переключателя (`<span>` с `switch`). */
79
83
  trackClassName?: string;
80
- /** Класс для перемещаемой ручки переключателя (`<div>` с `thumb`). */
84
+ /** Класс для перемещаемой ручки переключателя (`<span>` с `thumb`). */
81
85
  thumbClassName?: string;
82
- /** Класс для скрытого нативного `<input>`. */
86
+ /** Класс для визуально скрытого нативного `<input>` поверх трека. */
83
87
  inputClassName?: string;
84
88
  /** Класс для блока с заголовком/описанием. */
85
89
  labelsContainerClassName?: string;
@@ -1,140 +1,163 @@
1
- import { jsx as t, jsxs as l } from "react/jsx-runtime";
2
- import { cx as u } from "@emotion/css";
3
- import { forwardRef as B, useRef as D, useImperativeHandle as H, useCallback as T, useMemo as k } from "react";
4
- import q from "../../../assets/svg/switch/fire.svg.js";
5
- import A from "../../../assets/svg/switch/moon.svg.js";
6
- import { styles as r } from "./styles.js";
7
- import { useControlled as G } from "../../../utils/hooks/useControlled.js";
8
- import { Text as v } from "../../builders/Text/Text.js";
9
- import { Box as f } from "../../builders/Box/Box.js";
10
- import { theme as J } from "../../../utils/theme/index.js";
11
- const { spacing: b } = J, K = B(
1
+ import { jsx as t, jsxs as f } from "react/jsx-runtime";
2
+ import { cx as o } from "@emotion/css";
3
+ import { forwardRef as q, useId as A, useRef as G, useImperativeHandle as J, useCallback as K, useMemo as I } from "react";
4
+ import L from "../../../assets/svg/switch/fire.svg.js";
5
+ import O from "../../../assets/svg/switch/moon.svg.js";
6
+ import { styles as e } from "./styles.js";
7
+ import { useControlled as Q } from "../../../utils/hooks/useControlled.js";
8
+ import { Text as x } from "../../builders/Text/Text.js";
9
+ import { Box as U } from "../../builders/Box/Box.js";
10
+ import { theme as X } from "../../../utils/theme/index.js";
11
+ const { spacing: Y } = X, Z = q(
12
12
  ({
13
- title: a,
14
- text: i,
15
- icon: w,
16
- disabled: m,
17
- onChange: c,
18
- checked: x,
19
- defaultValue: d,
20
- className: S,
21
- wrapperClassName: R,
22
- switchContainerClassName: V,
23
- trackClassName: j,
24
- thumbClassName: C,
25
- inputClassName: E,
26
- labelsContainerClassName: F,
13
+ title: c,
14
+ text: s,
15
+ icon: B,
16
+ disabled: d,
17
+ onChange: n,
18
+ checked: S,
19
+ defaultValue: p,
20
+ className: $,
21
+ wrapperClassName: j,
22
+ switchContainerClassName: C,
23
+ trackClassName: R,
24
+ thumbClassName: V,
25
+ inputClassName: D,
26
+ labelsContainerClassName: E,
27
27
  titleClassName: h,
28
- textClassName: p,
29
- size: I = "md",
30
- variant: g = "accent",
28
+ textClassName: b,
29
+ size: F = "md",
30
+ variant: y = "accent",
31
31
  reverse: M = !1,
32
32
  w: W = "auto",
33
- ..._
34
- }, $) => {
35
- const [o, n] = G(x, d), s = D(null);
36
- H(
37
- $,
33
+ "aria-describedby": _,
34
+ ...g
35
+ }, z) => {
36
+ const [l, a] = Q(
37
+ S,
38
+ p ?? !1
39
+ ), N = A(), m = `${N}-title`, u = `${N}-text`, k = !!(c && s), H = [k && u, _].filter(Boolean).join(" ") || void 0, i = G(null);
40
+ J(
41
+ z,
38
42
  () => ({
39
43
  focus: () => {
40
- s.current?.focus();
44
+ i.current?.focus();
41
45
  },
42
46
  blur: () => {
43
- s.current?.blur();
47
+ i.current?.blur();
44
48
  },
45
49
  get checked() {
46
- return o ?? !1;
50
+ return l ?? !1;
47
51
  },
48
- setChecked: (e) => {
49
- n(e), c?.(e);
52
+ setChecked: (r) => {
53
+ a(r), n?.(r);
50
54
  },
51
55
  toggle: () => {
52
- const e = !o;
53
- n(e), c?.(e);
56
+ const r = !l;
57
+ a(r), n?.(r);
54
58
  },
55
- isFocused: () => document.activeElement === s.current,
56
- isDisabled: () => m ?? !1,
57
- getInputElement: () => s.current,
58
- checkValidity: () => s.current?.checkValidity() ?? !0,
59
+ isFocused: () => document.activeElement === i.current,
60
+ isDisabled: () => d ?? !1,
61
+ getInputElement: () => i.current,
62
+ checkValidity: () => i.current?.checkValidity() ?? !0,
59
63
  reset: () => {
60
- const e = d ?? !1;
61
- n(e), c?.(e);
64
+ const r = p ?? !1;
65
+ a(r), n?.(r);
62
66
  }
63
67
  }),
64
- [o, n, c, m, d]
68
+ [l, a, n, d, p]
65
69
  );
66
- const z = T(
67
- (e) => {
68
- n(e.target.checked), c?.(e.target.checked);
70
+ const T = K(
71
+ (r) => {
72
+ a(r.target.checked), n?.(r.target.checked);
69
73
  },
70
- [c]
71
- ), N = k(() => typeof a == "string" ? /* @__PURE__ */ t(
72
- v,
74
+ [n, a]
75
+ ), v = I(() => c ? typeof c == "string" ? /* @__PURE__ */ t(
76
+ x,
73
77
  {
78
+ id: m,
79
+ tag: "span",
74
80
  variant: "body-1",
75
81
  colorName: "primary",
76
82
  fontWeight: "medium",
77
83
  className: h,
78
- children: a
84
+ children: c
79
85
  }
80
- ) : a, [a, h]), y = k(() => typeof i == "string" ? /* @__PURE__ */ t(
81
- v,
86
+ ) : /* @__PURE__ */ t("span", { id: m, children: c }) : null, [c, h, m]), w = I(() => s ? typeof s == "string" ? /* @__PURE__ */ t(
87
+ x,
82
88
  {
89
+ id: u,
90
+ tag: "span",
83
91
  variant: "body-2",
84
92
  colorName: "secondary",
85
93
  fontWeight: "regular",
86
- className: p,
87
- children: i
94
+ className: b,
95
+ children: s
88
96
  }
89
- ) : i, [i, p]);
90
- return /* @__PURE__ */ l(f, { row: !0, w: W, gap: b.sm, className: R, children: [
91
- /* @__PURE__ */ l(
97
+ ) : /* @__PURE__ */ t("span", { id: u, children: s }) : null, [s, b, u]);
98
+ return /* @__PURE__ */ f(U, { row: !0, w: W, gap: Y.sm, className: j, children: [
99
+ /* @__PURE__ */ f(
92
100
  "label",
93
101
  {
94
- className: u(r.container, S, {
95
- [r.disabled]: m,
96
- [r.reverse]: M
102
+ className: o(e.container, $, {
103
+ [e.disabled]: d,
104
+ [e.reverse]: M
97
105
  }),
98
106
  children: [
99
- /* @__PURE__ */ l(
100
- f,
107
+ /* @__PURE__ */ f(
108
+ "span",
101
109
  {
102
- className: u(
103
- r.switchContainer,
104
- r[`size_${I}`],
105
- r[`variant_${g}`],
106
- V
110
+ className: o(
111
+ e.switchContainer,
112
+ e[`size_${F}`],
113
+ e[`variant_${y}`],
114
+ C
107
115
  ),
108
116
  children: [
109
117
  /* @__PURE__ */ t(
110
118
  "input",
111
119
  {
112
- ref: s,
120
+ ref: i,
113
121
  type: "checkbox",
114
- checked: o,
115
- hidden: !0,
116
- onChange: z,
117
- disabled: m,
118
- className: E,
119
- ..._
122
+ role: "switch",
123
+ checked: l,
124
+ onChange: T,
125
+ disabled: d,
126
+ "aria-labelledby": k && !g["aria-label"] ? m : void 0,
127
+ "aria-describedby": H,
128
+ className: o(e.input, D),
129
+ ...g
120
130
  }
121
131
  ),
122
- /* @__PURE__ */ t("div", { className: u(r.switch, j), children: /* @__PURE__ */ t("div", { className: u(r.thumb, C), children: g === "theme" && (o ? /* @__PURE__ */ t(A, { className: r.moon }) : /* @__PURE__ */ t(q, { className: r.fire })) }) })
132
+ /* @__PURE__ */ t(
133
+ "span",
134
+ {
135
+ "aria-hidden": "true",
136
+ className: o(e.switch, R),
137
+ children: /* @__PURE__ */ t("span", { className: o(e.thumb, V), children: y === "theme" && (l ? /* @__PURE__ */ t(O, { className: e.icon }) : /* @__PURE__ */ t(L, { className: e.icon })) })
138
+ }
139
+ )
123
140
  ]
124
141
  }
125
142
  ),
126
- (N || y) && /* @__PURE__ */ l(f, { gap: b.xs, className: F, children: [
127
- N,
128
- y
129
- ] })
143
+ (v || w) && /* @__PURE__ */ f(
144
+ "span",
145
+ {
146
+ className: o(e.labelsContainer, E),
147
+ children: [
148
+ v,
149
+ w
150
+ ]
151
+ }
152
+ )
130
153
  ]
131
154
  }
132
155
  ),
133
- w
156
+ B
134
157
  ] });
135
158
  }
136
159
  );
137
- K.displayName = "Switch";
160
+ Z.displayName = "Switch";
138
161
  export {
139
- K as Switch
162
+ Z as Switch
140
163
  };
@@ -3,6 +3,7 @@ export declare const styles: {
3
3
  disabled: string;
4
4
  reverse: string;
5
5
  switchContainer: string;
6
+ input: string;
6
7
  variant_accent: string;
7
8
  variant_secondary: string;
8
9
  variant_positive: string;
@@ -12,6 +13,6 @@ export declare const styles: {
12
13
  size_lg: string;
13
14
  switch: string;
14
15
  thumb: string;
15
- moon: string;
16
- fire: string;
16
+ labelsContainer: string;
17
+ icon: string;
17
18
  };
@@ -1,128 +1,131 @@
1
- import { css as i } from "@emotion/css";
2
- import { theme as c } from "../../../utils/theme/index.js";
3
- const { color: t, cornerRadius: r, height: h, spacing: s, transition: e } = c, l = {
4
- container: i({
1
+ import { css as t } from "@emotion/css";
2
+ import { theme as s } from "../../../utils/theme/index.js";
3
+ const { color: i, cornerRadius: h, height: e, spacing: r, transition: c } = s, l = {
4
+ container: t({
5
5
  width: "100%",
6
6
  cursor: "pointer",
7
- outline: "none",
8
7
  userSelect: "none",
9
8
  display: "flex",
10
9
  justifyContent: "space-between",
11
- gap: s.md,
12
- "&.disabled": {
13
- cursor: "not-allowed",
14
- opacity: 0.5
15
- },
16
- "&.reverse": {
17
- flexDirection: "row-reverse"
18
- }
10
+ gap: r.md
19
11
  }),
20
- disabled: i({
12
+ disabled: t({
21
13
  cursor: "not-allowed",
22
14
  opacity: 0.5
23
15
  }),
24
- reverse: i({
16
+ reverse: t({
25
17
  flexDirection: "row-reverse"
26
18
  }),
27
- switchContainer: i({
19
+ switchContainer: t({
20
+ position: "relative",
21
+ display: "flex",
22
+ flexShrink: 0,
23
+ alignSelf: "flex-start",
28
24
  "--width-switch-sm": "34px",
29
25
  "--width-switch-md": "40px",
30
26
  "--width-switch-lg": "52px",
31
27
  "--size-thumb-sm": "18px",
32
28
  "--size-thumb-md": "20px",
33
29
  "--size-thumb-lg": "28px",
34
- "--thumb-translate": "0px",
35
- "&:has(input:checked)": {
36
- "--thumb-translate": "calc(var(--switch-width) - var(--thumb-width) - 4px)"
37
- }
30
+ // Выключенное состояние у всех вариантов одинаковое
31
+ "--switch-bg-unchecked": i.fill.bg_tertiary,
32
+ "--thumb-bg-unchecked": i.fill.bg_primary
38
33
  }),
39
- // Variant classes — each handles both checked and unchecked states
40
- variant_accent: i({
41
- "&:has(input:not(:checked))": {
42
- "--switch-bg-color": t.fill.bg_tertiary,
43
- "--thumb-bg-color": t.fill.bg_primary
44
- },
45
- "&:has(input:checked)": {
46
- "--switch-bg-color": t.fill.accent_primary,
47
- "--thumb-bg-color": t.fill.bg_primary
48
- }
34
+ // Нативный input прозрачен и лежит поверх трека: остаётся в tab-порядке и
35
+ // в дереве доступности, а relative-контейнер не даёт фокусу прокручивать
36
+ // страницу к случайной точке.
37
+ input: t({
38
+ position: "absolute",
39
+ inset: 0,
40
+ width: "100%",
41
+ height: "100%",
42
+ margin: 0,
43
+ opacity: 0,
44
+ cursor: "inherit"
49
45
  }),
50
- variant_secondary: i({
51
- "&:has(input:not(:checked))": {
52
- "--switch-bg-color": t.fill.bg_tertiary,
53
- "--thumb-bg-color": t.fill.bg_primary
54
- },
55
- "&:has(input:checked)": {
56
- "--switch-bg-color": t.fill.accent_secondary,
57
- "--thumb-bg-color": t.fill.accent_primary
58
- }
46
+ // Variant classes — задают цвета включённого состояния
47
+ variant_accent: t({
48
+ "--switch-bg-checked": i.fill.accent_primary,
49
+ "--thumb-bg-checked": i.fill.bg_primary
59
50
  }),
60
- variant_positive: i({
61
- "&:has(input:not(:checked))": {
62
- "--switch-bg-color": t.fill.bg_tertiary,
63
- "--thumb-bg-color": t.fill.bg_primary
64
- },
65
- "&:has(input:checked)": {
66
- "--switch-bg-color": t.fill.positive_primary,
67
- "--thumb-bg-color": t.fill.bg_primary
68
- }
51
+ variant_secondary: t({
52
+ "--switch-bg-checked": i.fill.accent_secondary,
53
+ "--thumb-bg-checked": i.fill.accent_primary
69
54
  }),
70
- variant_theme: i({
71
- "&:has(input:not(:checked))": {
72
- "--switch-bg-color": t.fill.bg_tertiary,
73
- "--thumb-bg-color": t.fill.bg_primary
74
- },
75
- "&:has(input:checked)": {
76
- "--switch-bg-color": t.fill.bg_secondary,
77
- "--thumb-bg-color": t.fill.bg_tertiary
78
- }
55
+ variant_positive: t({
56
+ "--switch-bg-checked": i.fill.positive_primary,
57
+ "--thumb-bg-checked": i.fill.bg_primary
58
+ }),
59
+ variant_theme: t({
60
+ "--switch-bg-checked": i.fill.bg_secondary,
61
+ "--thumb-bg-checked": i.fill.bg_tertiary
79
62
  }),
80
63
  // Size classes
81
- size_sm: i({
64
+ size_sm: t({
82
65
  "--switch-width": "var(--width-switch-sm)",
83
- "--switch-height": h.switch.sm,
66
+ "--switch-height": e.switch.sm,
84
67
  "--thumb-width": "var(--size-thumb-sm)",
85
68
  "--thumb-height": "var(--size-thumb-sm)"
86
69
  }),
87
- size_md: i({
70
+ size_md: t({
88
71
  "--switch-width": "var(--width-switch-md)",
89
- "--switch-height": h.switch.md,
72
+ "--switch-height": e.switch.md,
90
73
  "--thumb-width": "var(--size-thumb-md)",
91
74
  "--thumb-height": "var(--size-thumb-md)"
92
75
  }),
93
- size_lg: i({
76
+ size_lg: t({
94
77
  "--switch-width": "var(--width-switch-lg)",
95
- "--switch-height": h.switch.lg,
78
+ "--switch-height": e.switch.lg,
96
79
  "--thumb-width": "var(--size-thumb-lg)",
97
80
  "--thumb-height": "var(--size-thumb-lg)"
98
81
  }),
99
- switch: i({
82
+ switch: t({
83
+ "--switch-bg-color": "var(--switch-bg-unchecked)",
84
+ "--thumb-bg-color": "var(--thumb-bg-unchecked)",
85
+ "--thumb-translate": "0px",
100
86
  width: "var(--switch-width)",
101
87
  height: "var(--switch-height)",
102
- transition: e.general,
88
+ transition: c.general,
89
+ transitionProperty: "background-color",
103
90
  display: "flex",
104
91
  alignItems: "center",
105
92
  padding: 2,
106
93
  backgroundColor: "var(--switch-bg-color)",
107
- borderRadius: r.xl
94
+ borderRadius: h.xl,
95
+ // Прозрачный outline не виден, но в режиме высокой контрастности
96
+ // (forced-colors) браузер красит его системным цветом — без него фон
97
+ // трека и ручки заменяется на Canvas и переключатель пропадает.
98
+ outline: "1px solid transparent",
99
+ "input:checked + &": {
100
+ "--switch-bg-color": "var(--switch-bg-checked)",
101
+ "--thumb-bg-color": "var(--thumb-bg-checked)",
102
+ "--thumb-translate": "calc(var(--switch-width) - var(--thumb-width) - 4px)"
103
+ },
104
+ "input:focus-visible + &": {
105
+ outline: `2px solid ${i.border.accent_primary}`,
106
+ outlineOffset: 2
107
+ }
108
108
  }),
109
- thumb: i({
109
+ thumb: t({
110
110
  display: "flex",
111
111
  alignItems: "center",
112
112
  justifyContent: "center",
113
113
  padding: 4,
114
- transition: e.general,
114
+ transition: c.general,
115
+ transitionProperty: "transform, background-color",
115
116
  transform: "translateX(var(--thumb-translate, 0))",
116
117
  width: "var(--thumb-width)",
117
118
  height: "var(--thumb-height)",
118
119
  backgroundColor: "var(--thumb-bg-color)",
119
- borderRadius: r.fullRound
120
+ borderRadius: h.fullRound,
121
+ outline: "1px solid transparent"
120
122
  }),
121
- moon: i({
122
- width: "100%",
123
- height: "100%"
123
+ labelsContainer: t({
124
+ display: "flex",
125
+ flexDirection: "column",
126
+ gap: r.xs
124
127
  }),
125
- fire: i({
128
+ icon: t({
126
129
  width: "100%",
127
130
  height: "100%"
128
131
  })