@prosopo/procaptcha-common 2.11.21 → 2.11.23

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Reload.d.ts","sourceRoot":"","sources":["../../src/reactComponents/Reload.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,EAAE,EAAqB,MAAM,OAAO,CAAC;AAE9E,UAAU,iBAAkB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC1E,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAaD,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAiE9C,CAAC"}
1
+ {"version":3,"file":"Reload.d.ts","sourceRoot":"","sources":["../../src/reactComponents/Reload.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,oBAAoB,EAAE,KAAK,EAAE,EAAqB,MAAM,OAAO,CAAC;AAE9E,UAAU,iBAAkB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IAC1E,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAaD,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC,iBAAiB,CAoE9C,CAAC"}
@@ -4,17 +4,17 @@ import { jsx, jsxs } from "@emotion/react/jsx-runtime";
4
4
  //#region src/reactComponents/Reload.tsx
5
5
  var buttonStyleBase = {
6
6
  border: "none",
7
- paddingTop: "6px",
8
- paddingBottom: "6px",
7
+ padding: "8px",
9
8
  cursor: "pointer",
10
- height: "39px",
11
- width: "39px",
9
+ height: "40px",
10
+ width: "40px",
12
11
  borderRadius: "50%",
13
12
  display: "flex"
14
13
  };
15
14
  var ReloadButton = ({ themeColor, onReload }) => {
16
15
  const theme = useMemo(() => themeColor === "light" ? lightTheme : darkTheme, [themeColor]);
17
16
  const [hover, setHover] = useState(false);
17
+ const [focusVisible, setFocusVisible] = useState(false);
18
18
  return /* @__PURE__ */ jsx("button", {
19
19
  className: "reload-button",
20
20
  "aria-label": "Reload",
@@ -22,27 +22,33 @@ var ReloadButton = ({ themeColor, onReload }) => {
22
22
  style: useMemo(() => {
23
23
  return {
24
24
  ...buttonStyleBase,
25
- backgroundColor: theme.palette.background.default,
26
- color: hover ? theme.palette.primary.contrastText : theme.palette.background.contrastText,
27
- border: `1px solid ${theme.palette.grey[500]}`,
28
- borderRadius: "50%",
29
- transition: "background-color 0.3s",
30
- boxShadow: `0px 1px 3px 0px ${theme.palette.grey[500]}`,
25
+ ...focusVisible ? {
26
+ outline: `3px solid ${theme.palette.primary.main}`,
27
+ outlineOffset: "2px"
28
+ } : {},
29
+ backgroundColor: hover ? theme.palette.primaryContainer.hover : theme.palette.primaryContainer.main,
30
+ color: theme.palette.primaryContainer.contrastText,
31
+ transition: "background-color 0.25s",
31
32
  justifyContent: "center",
32
33
  alignItems: "center",
33
- margin: "0 auto",
34
- backgroundColor: hover ? theme.palette.grey[700] : theme.palette.background.default
34
+ margin: "0 auto"
35
35
  };
36
- }, [hover, theme]),
36
+ }, [
37
+ hover,
38
+ theme,
39
+ focusVisible
40
+ ]),
37
41
  onMouseEnter: () => setHover(true),
38
42
  onMouseLeave: () => setHover(false),
43
+ onFocus: (e) => setFocusVisible(e.target.matches(":focus-visible")),
44
+ onBlur: () => setFocusVisible(false),
39
45
  onClick: (e) => {
40
46
  e.preventDefault();
41
47
  onReload();
42
48
  },
43
49
  children: /* @__PURE__ */ jsxs("svg", {
44
- width: "16px",
45
- height: "16px",
50
+ width: "24px",
51
+ height: "24px",
46
52
  viewBox: "0 0 16 16",
47
53
  version: "1.1",
48
54
  xmlns: "http://www.w3.org/2000/svg",
@@ -50,7 +56,7 @@ var ReloadButton = ({ themeColor, onReload }) => {
50
56
  style: { display: "flex" },
51
57
  children: [/* @__PURE__ */ jsx("title", { children: "reload" }), /* @__PURE__ */ jsx("path", {
52
58
  shapeRendering: "optimizeQuality",
53
- fill: hover ? theme.palette.primary.contrastText : theme.palette.grey[700],
59
+ fill: theme.palette.primaryContainer.contrastText,
54
60
  transform: "scale(0.0416)",
55
61
  d: "M234.666667,149.333333 L234.666667,106.666667 L314.564847,106.664112 C287.579138,67.9778918 242.745446,42.6666667 192,42.6666667 C109.525477,42.6666667 42.6666667,109.525477 42.6666667,192 C42.6666667,274.474523 109.525477,341.333333 192,341.333333 C268.201293,341.333333 331.072074,284.258623 340.195444,210.526102 L382.537159,215.817985 C370.807686,310.617565 289.973536,384 192,384 C85.961328,384 1.42108547e-14,298.038672 1.42108547e-14,192 C1.42108547e-14,85.961328 85.961328,1.42108547e-14 192,1.42108547e-14 C252.316171,1.42108547e-14 306.136355,27.8126321 341.335366,71.3127128 L341.333333,1.42108547e-14 L384,1.42108547e-14 L384,149.333333 L234.666667,149.333333 Z"
56
62
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"Reload.js","sourceRoot":"","sources":["../../src/reactComponents/Reload.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Reload.js","sourceRoot":"","sources":["../../src/reactComponents/Reload.tsx"],"names":[],"mappings":";AAcA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAsC,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAQ9E,MAAM,eAAe,GAAG;IACvB,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,MAAM;IACb,YAAY,EAAE,KAAK;IACnB,OAAO,EAAE,MAAM;CACf,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAA0B,CAAC,EACnD,UAAU,EACV,QAAQ,GACW,EAAE,EAAE;IACvB,MAAM,KAAK,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,EACvD,CAAC,UAAU,CAAC,CACZ,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAG1C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAChC,OAAO;YACN,GAAG,eAAe;YAClB,GAAG,CAAC,YAAY;gBACf,CAAC,CAAC;oBAEA,OAAO,EAAE,aAAa,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE;oBAClD,aAAa,EAAE,KAAK;iBACpB;gBACF,CAAC,CAAC,EAAE,CAAC;YAGN,eAAe,EAAE,KAAK;gBACrB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK;gBACtC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI;YACtC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,YAAY;YAClD,UAAU,EAAE,wBAAwB;YACpC,cAAc,EAAE,QAAQ;YACxB,UAAU,EAAE,QAAQ;YACpB,MAAM,EAAE,QAAQ;SAChB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IACjC,OAAO,CACN,iBACC,SAAS,EAAC,eAAe,gBACd,QAAQ,EACnB,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAClC,YAAY,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EACnC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,EACnE,MAAM,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,EACpC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;YACd,CAAC,CAAC,cAAc,EAAE,CAAC;YACnB,QAAQ,EAAE,CAAC;QACZ,CAAC,YAED,eACC,KAAK,EAAC,MAAM,EACZ,MAAM,EAAC,MAAM,EACb,OAAO,EAAC,WAAW,EACnB,OAAO,EAAC,KAAK,EACb,KAAK,EAAC,4BAA4B,EAClC,UAAU,EAAC,8BAA8B,EACzC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAE1B,qCAAqB,EACrB,eACC,cAAc,EAAC,iBAAiB,EAChC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,YAAY,EACjD,SAAS,EAAE,eAAe,EAC1B,CAAC,EAAC,mqBAAmqB,GACpqB,IACG,GACE,CACT,CAAC;AACH,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prosopo/procaptcha-common",
3
- "version": "2.11.21",
3
+ "version": "2.11.23",
4
4
  "author": "PROSOPO LIMITED <info@prosopo.io>",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -34,10 +34,10 @@
34
34
  "dependencies": {
35
35
  "@emotion/react": "11.11.1",
36
36
  "@emotion/styled": "11.11.0",
37
- "@prosopo/account": "2.8.67",
38
- "@prosopo/load-balancer": "2.10.18",
39
- "@prosopo/types": "5.0.1",
40
- "@prosopo/widget-skeleton": "2.8.5",
37
+ "@prosopo/account": "2.8.69",
38
+ "@prosopo/load-balancer": "2.10.20",
39
+ "@prosopo/types": "5.0.3",
40
+ "@prosopo/widget-skeleton": "2.8.6",
41
41
  "react": "18.3.1",
42
42
  "react-dom": "18.3.1"
43
43
  },
@@ -0,0 +1,46 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ // Replaced at bundle time by vite's `define` — see the `define` block in
16
+ // packages/procaptcha-bundle/vite.config.ts. Typed as possibly undefined
17
+ // because a build that doesn't define it leaves the identifier undeclared,
18
+ // which is why the read below is guarded by `typeof`.
19
+ declare const __PROSOPO_ALLOW_UNTRUSTED_EVENTS__: boolean | undefined;
20
+
21
+ export interface TrustableEvent {
22
+ readonly isTrusted: boolean;
23
+ }
24
+
25
+ /**
26
+ * Whether an event came from a real user rather than a script.
27
+ *
28
+ * The widget acts only on trusted input: a synthetic event is precisely how a
29
+ * bot drives a page, so honouring one would hand over the captcha for free.
30
+ *
31
+ * The single exception is the firefox cypress leg. cypress can only dispatch
32
+ * trusted input through the chrome devtools protocol, which it exposes for
33
+ * chromium browsers only, so on firefox every spec clicks synthetically.
34
+ * Building with PROSOPO_ALLOW_UNTRUSTED_EVENTS=1 opens the gate for that leg.
35
+ * Production builds pin the constant to false and the branch folds away, so
36
+ * the allowance cannot reach a shipped bundle.
37
+ */
38
+ export function isEventTrusted(event: TrustableEvent): boolean {
39
+ if (event.isTrusted) {
40
+ return true;
41
+ }
42
+ return (
43
+ typeof __PROSOPO_ALLOW_UNTRUSTED_EVENTS__ !== "undefined" &&
44
+ __PROSOPO_ALLOW_UNTRUSTED_EVENTS__
45
+ );
46
+ }
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@
13
13
  // limitations under the License.
14
14
 
15
15
  export * from "./providers.js";
16
+ export * from "./events/trust.js";
16
17
  export * from "./state/builder.js";
17
18
  export * from "./callbacks/defaultCallbacks.js";
18
19
  export * from "./callbacks/defaultEvents.js";
@@ -17,6 +17,7 @@ import styled from "@emotion/styled";
17
17
  import {
18
18
  type Theme,
19
19
  WIDGET_CHECKBOX_SPINNER_CSS_CLASS,
20
+ withAlpha,
20
21
  } from "@prosopo/widget-skeleton";
21
22
  import {
22
23
  type ButtonHTMLAttributes,
@@ -25,6 +26,7 @@ import {
25
26
  useMemo,
26
27
  useState,
27
28
  } from "react";
29
+ import { isEventTrusted } from "../events/trust.js";
28
30
 
29
31
  interface CheckboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
30
32
  theme: Theme;
@@ -45,20 +47,34 @@ const checkboxBefore = css`{
45
47
  }
46
48
  }`;
47
49
 
50
+ // In forced-colors mode (Windows High Contrast) backgrounds are overridden, so
51
+ // the custom-painted tick can disappear — fall back to the native control,
52
+ // which the OS draws in system colors. !important beats the inline styles.
53
+ const checkboxForcedColors = css`
54
+ @media (forced-colors: active) {
55
+ appearance: auto !important;
56
+ background-image: none !important;
57
+ }
58
+ `;
59
+
60
+ // 28px container with a 2dp stroke, centred in a 58px touch target. Larger
61
+ // than the 18dp M3 checkbox spec — kept at the original size deliberately, as
62
+ // the widget needs a more prominent target than a form checkbox.
63
+ const CHECKBOX_SIZE = "28px";
64
+
48
65
  const baseStyle: CSSProperties = {
49
- width: "28px",
50
- height: "28px",
51
- minWidth: "14px",
52
- minHeight: "14px",
66
+ width: CHECKBOX_SIZE,
67
+ height: CHECKBOX_SIZE,
68
+ minWidth: CHECKBOX_SIZE,
69
+ minHeight: CHECKBOX_SIZE,
53
70
  top: "auto",
54
71
  left: "auto",
55
72
  opacity: "1",
56
- borderRadius: "12.5%",
57
73
  appearance: "none",
58
74
  cursor: "pointer",
59
75
  margin: "0",
60
76
  borderStyle: "solid",
61
- borderWidth: "1px",
77
+ borderWidth: "2px",
62
78
  };
63
79
 
64
80
  const ID_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -86,14 +102,24 @@ export const Checkbox: FC<CheckboxProps> = ({
86
102
  error,
87
103
  loading,
88
104
  }: CheckboxProps) => {
89
- const checkboxStyleBase: CSSProperties = {
90
- ...baseStyle,
91
- border: `1px solid ${theme.palette.background.contrastText}`,
92
- };
93
105
  const [hover, setHover] = useState(false);
94
106
 
107
+ // M3 focus indicator: a 3dp outline offset by 2dp, drawn only for keyboard
108
+ // focus. The control previously had no focus affordance at all.
109
+ const checkboxFocus = useMemo(
110
+ () => css`
111
+ &:focus-visible {
112
+ outline: 3px solid ${theme.palette.primary.main};
113
+ outline-offset: 2px;
114
+ }
115
+ `,
116
+ [theme],
117
+ );
118
+
95
119
  const ResponsiveLabel = styled.label<ResponsiveLabelProps>`
96
- color: ${theme.palette.background.contrastText};
120
+ /* The label sits on the widget surface, so it takes onSurface — not the
121
+ dialog container's on-colour. */
122
+ color: ${theme.palette.onSurface};
97
123
  position: relative;
98
124
  display: flex !important;
99
125
  cursor: pointer;
@@ -117,18 +143,37 @@ export const Checkbox: FC<CheckboxProps> = ({
117
143
  }
118
144
  `;
119
145
 
120
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO fix
121
146
  const checkboxStyle: CSSProperties = useMemo(() => {
147
+ // White (token) tick painted directly onto the box so the checked state is
148
+ // identical in light and dark mode — the native control can't be themed.
149
+ const tickColor = encodeURIComponent(theme.palette.checkbox.tick);
150
+ const checkImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='${tickColor}' d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E")`;
151
+ // M3 hover feedback is a state layer — the on-colour at 8% expressed as a
152
+ // spread ring around the container — not a change of stroke colour.
153
+ const stateLayerColor = checked
154
+ ? theme.palette.checkbox.fill
155
+ : theme.palette.onSurface;
122
156
  return {
123
- ...checkboxStyleBase,
124
- borderColor: hover
125
- ? theme.palette.background.contrastText
126
- : theme.palette.border,
127
- appearance: checked ? "auto" : "none",
128
- flex: 1,
157
+ ...baseStyle,
158
+ // 15px each side around a 28px box gives a 58px touch target.
129
159
  margin: "15px",
130
- minWidth: "28px",
131
- minHeight: "28px",
160
+ borderRadius: theme.shape.checkbox,
161
+ borderColor: checked
162
+ ? theme.palette.checkbox.fill
163
+ : theme.palette.checkbox.border,
164
+ backgroundColor: checked
165
+ ? theme.palette.checkbox.fill
166
+ : theme.palette.surface,
167
+ backgroundImage: checked ? checkImage : "none",
168
+ backgroundRepeat: "no-repeat",
169
+ backgroundPosition: "center",
170
+ // Tick inset within the 28px container.
171
+ backgroundSize: "20px 20px",
172
+ boxShadow: hover
173
+ ? `0 0 0 10px ${withAlpha(stateLayerColor, theme.stateLayer.hover)}`
174
+ : "none",
175
+ transition:
176
+ "background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease",
132
177
  };
133
178
  }, [hover, theme, checked]);
134
179
  const id = generateRandomId();
@@ -152,12 +197,12 @@ export const Checkbox: FC<CheckboxProps> = ({
152
197
  id={id}
153
198
  onMouseEnter={() => setHover(true)}
154
199
  onMouseLeave={() => setHover(false)}
155
- css={checkboxBefore}
200
+ css={[checkboxBefore, checkboxForcedColors, checkboxFocus]}
156
201
  type={"checkbox"}
157
202
  aria-live={"assertive"}
158
203
  aria-label={labelText}
159
204
  onKeyDown={(e) => {
160
- if (!e.isTrusted) {
205
+ if (!isEventTrusted(e)) {
161
206
  return;
162
207
  }
163
208
  if (e.key === "Enter") {
@@ -168,7 +213,7 @@ export const Checkbox: FC<CheckboxProps> = ({
168
213
  }
169
214
  }}
170
215
  onChange={(e) => {
171
- if (!e.isTrusted) {
216
+ if (!isEventTrusted(e)) {
172
217
  return;
173
218
  }
174
219
  e.preventDefault();
@@ -20,13 +20,13 @@ interface ReloadButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
20
20
  onReload: () => void;
21
21
  }
22
22
 
23
+ // M3 icon button: a 40dp circular container holding a 24dp icon.
23
24
  const buttonStyleBase = {
24
25
  border: "none",
25
- paddingTop: "6px",
26
- paddingBottom: "6px",
26
+ padding: "8px",
27
27
  cursor: "pointer",
28
- height: "39px",
29
- width: "39px",
28
+ height: "40px",
29
+ width: "40px",
30
30
  borderRadius: "50%",
31
31
  display: "flex",
32
32
  };
@@ -40,28 +40,31 @@ export const ReloadButton: FC<ReloadButtonProps> = ({
40
40
  [themeColor],
41
41
  );
42
42
  const [hover, setHover] = useState(false);
43
+ // M3 requires a visible focus indicator; matched imperatively so the ring is
44
+ // keyboard-only.
45
+ const [focusVisible, setFocusVisible] = useState(false);
43
46
  const buttonStyle = useMemo(() => {
44
- const baseStyle = {
47
+ return {
45
48
  ...buttonStyleBase,
46
- backgroundColor: theme.palette.background.default,
47
- color: hover
48
- ? theme.palette.primary.contrastText
49
- : theme.palette.background.contrastText,
50
- border: `1px solid ${theme.palette.grey[500]}`,
51
- borderRadius: "50%",
52
- transition: "background-color 0.3s",
53
- boxShadow: `0px 1px 3px 0px ${theme.palette.grey[500]}`,
49
+ ...(focusVisible
50
+ ? {
51
+ // M3 focus indicator: 3dp outline, 2dp offset.
52
+ outline: `3px solid ${theme.palette.primary.main}`,
53
+ outlineOffset: "2px",
54
+ }
55
+ : {}),
56
+ // Material 3 tonal icon button; hover swaps to the state-layer fill so
57
+ // the feedback is visible in dark mode too (a brightness filter is not).
58
+ backgroundColor: hover
59
+ ? theme.palette.primaryContainer.hover
60
+ : theme.palette.primaryContainer.main,
61
+ color: theme.palette.primaryContainer.contrastText,
62
+ transition: "background-color 0.25s",
54
63
  justifyContent: "center",
55
64
  alignItems: "center",
56
65
  margin: "0 auto",
57
66
  };
58
- return {
59
- ...baseStyle,
60
- backgroundColor: hover
61
- ? theme.palette.grey[700]
62
- : theme.palette.background.default,
63
- };
64
- }, [hover, theme]);
67
+ }, [hover, theme, focusVisible]);
65
68
  return (
66
69
  <button
67
70
  className="reload-button"
@@ -70,14 +73,16 @@ export const ReloadButton: FC<ReloadButtonProps> = ({
70
73
  style={buttonStyle}
71
74
  onMouseEnter={() => setHover(true)}
72
75
  onMouseLeave={() => setHover(false)}
76
+ onFocus={(e) => setFocusVisible(e.target.matches(":focus-visible"))}
77
+ onBlur={() => setFocusVisible(false)}
73
78
  onClick={(e) => {
74
79
  e.preventDefault();
75
80
  onReload();
76
81
  }}
77
82
  >
78
83
  <svg
79
- width="16px"
80
- height="16px"
84
+ width="24px"
85
+ height="24px"
81
86
  viewBox="0 0 16 16"
82
87
  version="1.1"
83
88
  xmlns="http://www.w3.org/2000/svg"
@@ -87,9 +92,7 @@ export const ReloadButton: FC<ReloadButtonProps> = ({
87
92
  <title>reload</title>
88
93
  <path
89
94
  shapeRendering="optimizeQuality"
90
- fill={
91
- hover ? theme.palette.primary.contrastText : theme.palette.grey[700]
92
- }
95
+ fill={theme.palette.primaryContainer.contrastText}
93
96
  transform={"scale(0.0416)"}
94
97
  d="M234.666667,149.333333 L234.666667,106.666667 L314.564847,106.664112 C287.579138,67.9778918 242.745446,42.6666667 192,42.6666667 C109.525477,42.6666667 42.6666667,109.525477 42.6666667,192 C42.6666667,274.474523 109.525477,341.333333 192,341.333333 C268.201293,341.333333 331.072074,284.258623 340.195444,210.526102 L382.537159,215.817985 C370.807686,310.617565 289.973536,384 192,384 C85.961328,384 1.42108547e-14,298.038672 1.42108547e-14,192 C1.42108547e-14,85.961328 85.961328,1.42108547e-14 192,1.42108547e-14 C252.316171,1.42108547e-14 306.136355,27.8126321 341.335366,71.3127128 L341.333333,1.42108547e-14 L384,1.42108547e-14 L384,149.333333 L234.666667,149.333333 Z"
95
98
  />