@joinskips/brand 5.0.0-develop.118 → 5.0.0-develop.120

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.
@@ -62,6 +62,9 @@ export declare function CodeStep({ words, length, noMatch, ranOut, resendAfterMs
62
62
  onResend: () => void;
63
63
  onChangeEmail: () => void;
64
64
  }): import("react").JSX.Element;
65
+ export declare const DOT_REST_OPACITY = 0.35;
66
+ export declare const DOT_RISE = 3;
67
+ export declare const DOT_STILL_OPACITY = 0.6;
65
68
  /** Skip writing a reply: three dots rising in turn in Skip's bubble beside Skip's mark, announced as a status. */
66
69
  export declare function SkipTyping({ label, skipMark }: {
67
70
  label: string;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
3
- import { ActivityIndicator, Animated, Image, Pressable, Text, TextInput, View } from 'react-native';
2
+ import { useEffect, useId, useRef, useState } from 'react';
3
+ import { AccessibilityInfo, ActivityIndicator, Animated, Image, Pressable, Text, TextInput, View } from 'react-native';
4
4
  import { DOT_CYCLE_MS } from "../skipTyping.js";
5
5
  import { codeDigits, completesCode, EMAIL_MAX_LENGTH, givenEmail, givenName, NAME_MAX_LENGTH, RESEND_AFTER_MS, resendClock, } from "../welcome.js";
6
6
  import { useColor } from "./parts.js";
@@ -23,10 +23,16 @@ export function Choices({ items, question, sending, holding = false, failed, wor
23
23
  onChoose(item);
24
24
  } }, item.value))) }), failed ? (_jsx(Text, { role: "alert", className: "font-ui text-sm text-error-ink", children: words.choiceNotSent })) : null] }));
25
25
  }
26
+ // The error under a field, as the field's description. React Native's types leave aria-describedby out, and
27
+ // react-native-web reads it.
28
+ function describedBy(errorId) {
29
+ return errorId === undefined ? {} : { 'aria-describedby': errorId };
30
+ }
26
31
  // One thing asked of a new visitor: a field in focus and its button. What is typed is checked here and handed on.
27
32
  function StepField({ label, error, submitLabel, input, usable, onSubmit, initial = '', aside }) {
28
33
  const [typed, setTyped] = useState(initial);
29
34
  const [refused, setRefused] = useState(false);
35
+ const errorId = useId();
30
36
  const placeholder = useColor('ink-placeholder');
31
37
  const submit = () => {
32
38
  const value = usable(typed);
@@ -34,7 +40,7 @@ function StepField({ label, error, submitLabel, input, usable, onSubmit, initial
34
40
  if (value !== null)
35
41
  onSubmit(value);
36
42
  };
37
- return (_jsxs(View, { className: "w-full max-w-[360px] gap-2 self-center", children: [aside ? (_jsx(View, { className: "self-center", children: _jsx(Pill, { label: aside.label, look: "flat", onPress: aside.onPress }) })) : null, _jsx(Text, { className: "font-ui text-sm font-medium text-ink", children: label }), _jsx(TextInput, { autoFocus: true, value: typed, onChangeText: setTyped, onSubmitEditing: submit, accessibilityLabel: label, "aria-invalid": refused, placeholderTextColor: placeholder, className: `h-control rounded-control border bg-surface px-3 font-ui text-md text-ink ${refused ? 'border-error-ink' : 'border-line-control'}`, ...input }), refused ? (_jsx(Text, { role: "alert", className: "font-ui text-sm text-error-ink", children: error })) : null, _jsx(View, { className: "self-center", children: _jsx(Pill, { label: submitLabel, onPress: submit }) })] }));
43
+ return (_jsxs(View, { className: "w-full max-w-[360px] gap-2 self-center", children: [aside ? (_jsx(View, { className: "self-center", children: _jsx(Pill, { label: aside.label, look: "flat", onPress: aside.onPress }) })) : null, _jsx(Text, { className: "font-ui text-sm font-medium text-ink", children: label }), _jsx(TextInput, { autoFocus: true, value: typed, onChangeText: setTyped, onSubmitEditing: submit, accessibilityLabel: label, "aria-invalid": refused, ...describedBy(refused ? errorId : undefined), placeholderTextColor: placeholder, className: `h-control rounded-control border bg-surface px-3 font-ui text-md text-ink ${refused ? 'border-error-ink' : 'border-line-control'}`, ...input }), refused ? (_jsx(Text, { nativeID: errorId, role: "alert", className: "font-ui text-sm text-error-ink", children: error })) : null, _jsx(View, { className: "self-center", children: _jsx(Pill, { label: submitLabel, onPress: submit }) })] }));
38
44
  }
39
45
  /** The email asked for after Continue here, and again for a visitor changing it. */
40
46
  export function EmailStep({ words, onSubmit, initial, }) {
@@ -68,23 +74,55 @@ function NewCode({ waitMs, words, onResend }) {
68
74
  */
69
75
  export function CodeStep({ words, length, noMatch, ranOut, resendAfterMs = RESEND_AFTER_MS, onSubmit, onResend, onChangeEmail, }) {
70
76
  const [code, setCode] = useState('');
77
+ const [focused, setFocused] = useState(false);
78
+ const errorId = useId();
71
79
  const error = ranOut ? words.codeRanOut : noMatch ? words.codeNoMatch : null;
72
- return (_jsxs(View, { className: "w-full max-w-[360px] items-center gap-3 self-center", children: [_jsx(Text, { className: "font-ui text-sm font-medium text-ink", children: words.codeLabel }), _jsxs(View, { className: "relative", children: [_jsx(View, { "aria-hidden": true, className: "flex-row gap-2", children: Array.from({ length }, (_, box) => (_jsx(View, { className: `h-control w-11 items-center justify-center rounded-control border ${error ? 'border-error-ink' : 'border-line-control'}`, children: _jsx(Text, { className: "font-ui text-xl font-medium text-ink", children: code[box] ?? '' }) }, box))) }), _jsx(TextInput, { autoFocus: true, value: code, onChangeText: (typed) => {
80
+ // The box the next digit goes in, or the last once the code is whole.
81
+ const nextBox = Math.min(code.length, length - 1);
82
+ return (_jsxs(View, { className: "w-full max-w-[360px] items-center gap-3 self-center", children: [_jsx(Text, { className: "font-ui text-sm font-medium text-ink", children: words.codeLabel }), _jsxs(View, { className: "relative", children: [_jsx(View, { "aria-hidden": true, className: "flex-row gap-2", children: Array.from({ length }, (_, box) => (_jsx(View, { className: `h-control w-11 items-center justify-center rounded-control border ${focused && box === nextBox ? 'border-focus' : error ? 'border-error-ink' : 'border-line-control'}`, children: _jsx(Text, { className: "font-ui text-xl font-medium text-ink", children: code[box] ?? '' }) }, box))) }), _jsx(TextInput, { autoFocus: true, value: code, onChangeText: (typed) => {
73
83
  const digits = codeDigits(typed, length);
74
84
  setCode(digits);
75
85
  if (completesCode(digits, code, length))
76
86
  onSubmit(digits);
77
- }, accessibilityLabel: words.codeLabel, "aria-invalid": error !== null, inputMode: "numeric", autoComplete: "one-time-code", textContentType: "oneTimeCode", caretHidden: true, className: "absolute inset-0 rounded-control text-transparent" })] }), error ? (_jsx(Text, { role: "alert", className: "text-center font-ui text-sm text-error-ink", children: error })) : null, _jsxs(View, { className: "flex-row flex-wrap items-center justify-center gap-2", children: [_jsx(Pill, { label: words.changeEmail, look: "flat", onPress: onChangeEmail }), _jsx(NewCode, { waitMs: resendAfterMs, words: words, onResend: onResend }), _jsx(Pill, { label: words.codeSubmit, onPress: () => {
87
+ }, onFocus: () => {
88
+ setFocused(true);
89
+ }, onBlur: () => {
90
+ setFocused(false);
91
+ }, accessibilityLabel: words.codeLabel, "aria-invalid": error !== null, ...describedBy(error === null ? undefined : errorId), inputMode: "numeric", autoComplete: "one-time-code", textContentType: "oneTimeCode", caretHidden: true,
92
+ // Unseen, not just clear: the boxes draw the digits and the focus, and a checker would read clear text as
93
+ // text with no contrast.
94
+ className: "absolute inset-0 opacity-0" })] }), error ? (_jsx(Text, { nativeID: errorId, role: "alert", className: "text-center font-ui text-sm text-error-ink", children: error })) : null, _jsxs(View, { className: "flex-row flex-wrap items-center justify-center gap-2", children: [_jsx(Pill, { label: words.changeEmail, look: "flat", onPress: onChangeEmail }), _jsx(NewCode, { waitMs: resendAfterMs, words: words, onResend: onResend }), _jsx(Pill, { label: words.codeSubmit, disabled: code.length < length, onPress: () => {
78
95
  onSubmit(code);
79
96
  } })] })] }));
80
97
  }
81
98
  const DOTS = [0, 1, 2];
82
99
  const DOT_SIZE = 6;
100
+ // A dot at rest is faint, rises this far at the top of its turn, and holds at the still strength for a visitor who
101
+ // asks for less motion.
102
+ export const DOT_REST_OPACITY = 0.35;
103
+ export const DOT_RISE = 3;
104
+ export const DOT_STILL_OPACITY = 0.6;
105
+ // Null until the device has answered, so the dots never start before a visitor who wants them still is known.
106
+ function useReducedMotion() {
107
+ const [reduced, setReduced] = useState(null);
108
+ useEffect(() => {
109
+ void AccessibilityInfo.isReduceMotionEnabled().then(setReduced);
110
+ const change = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduced);
111
+ return () => {
112
+ change.remove();
113
+ };
114
+ }, []);
115
+ return reduced;
116
+ }
83
117
  /** Skip writing a reply: three dots rising in turn in Skip's bubble beside Skip's mark, announced as a status. */
84
118
  export function SkipTyping({ label, skipMark }) {
85
119
  const dotColor = useColor('ink-secondary');
120
+ const reduced = useReducedMotion();
121
+ const still = reduced === true;
86
122
  const rise = useRef(DOTS.map(() => new Animated.Value(0))).current;
87
123
  useEffect(() => {
124
+ if (reduced !== false)
125
+ return undefined;
88
126
  const step = DOT_CYCLE_MS / (2 * DOTS.length);
89
127
  const loop = Animated.loop(Animated.stagger(step, rise.map((value) => Animated.sequence([
90
128
  Animated.timing(value, { toValue: 1, duration: step, useNativeDriver: true }),
@@ -94,12 +132,17 @@ export function SkipTyping({ label, skipMark }) {
94
132
  return () => {
95
133
  loop.stop();
96
134
  };
97
- }, [rise]);
135
+ }, [rise, reduced]);
98
136
  return (_jsxs(View, { className: "mt-3 flex-row items-end gap-2", children: [_jsx(Image, { source: skipMark, "aria-hidden": true, style: { width: 32, height: 32 }, className: "rounded-control" }), _jsx(View, { role: "status", accessibilityLabel: label, className: "flex-row items-center gap-1 rounded-[20px] rounded-bl-[7px] border border-line-subtle bg-surface px-4 py-4", children: rise.map((value, dot) => (_jsx(Animated.View, { style: {
99
137
  width: DOT_SIZE,
100
138
  height: DOT_SIZE,
101
139
  borderRadius: DOT_SIZE / 2,
102
140
  backgroundColor: dotColor,
103
- transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [0, -DOT_SIZE] }) }],
141
+ ...(still
142
+ ? { opacity: DOT_STILL_OPACITY }
143
+ : {
144
+ opacity: value.interpolate({ inputRange: [0, 1], outputRange: [DOT_REST_OPACITY, 1] }),
145
+ transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [0, -DOT_RISE] }) }],
146
+ }),
104
147
  } }, DOTS[dot]))) })] }));
105
148
  }
package/manifest.json CHANGED
@@ -409,6 +409,10 @@
409
409
  "sha256": "6606d135bbbd06103bacef4cb89bf4019e53e092dcd19b51278edcbab1786b9d",
410
410
  "bytes": 130031
411
411
  },
412
+ "logos/png-2048/skips-wordmark-dark-2048.png": {
413
+ "sha256": "286ed3c2b3d37b8559ab773a692eca07d04d843ae093820c94ce5bdadff78713",
414
+ "bytes": 46330
415
+ },
412
416
  "logos/png-2048/skips-wordmark-light-2048.png": {
413
417
  "sha256": "61da312bae3aec7dddef398494fa98d13de940c81b3ecdb038c6904d26d85506",
414
418
  "bytes": 59707
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joinskips/brand",
3
- "version": "5.0.0-develop.118",
3
+ "version": "5.0.0-develop.120",
4
4
  "description": "Skips design tokens, Onest, logos, favicons, interface icons, member pictures and app icons.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://brand.joinskips.com",
@@ -20,10 +20,10 @@
20
20
  "validator": "^13.15.35"
21
21
  },
22
22
  "peerDependencies": {
23
- "react": "^19.3.0",
23
+ "react": "^19.0.0",
24
24
  "react-native": ">=0.81.0",
25
- "react-native-web": "~0.21.4",
26
- "tailwindcss": "^4.3.3",
25
+ "react-native-web": "~0.21.0",
26
+ "tailwindcss": "^4.0.0",
27
27
  "uniwind": "1.13.2"
28
28
  },
29
29
  "peerDependenciesMeta": {