@byline/ui 3.15.2 → 3.16.1

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.
@@ -32,6 +32,10 @@
32
32
  outline-color: var(--ring-color);
33
33
  }
34
34
 
35
+ :is(.checkbox-p0iZ1T:focus-visible, .byline-checkbox:focus-visible) {
36
+ outline-color: var(--checkbox-variant-outline-ring-color);
37
+ }
38
+
35
39
  :is(.checkbox-p0iZ1T[data-disabled], .byline-checkbox[data-disabled]) {
36
40
  pointer-events: none;
37
41
  }
@@ -1,8 +1,9 @@
1
1
  import type React from 'react';
2
2
  interface HelpTextProps {
3
+ id?: string;
3
4
  className?: string;
4
5
  size?: 'sm' | 'md' | 'lg';
5
6
  text: string;
6
7
  }
7
- export declare function HelpText({ className, size, text }: HelpTextProps): React.JSX.Element;
8
+ export declare function HelpText({ id, className, size, text }: HelpTextProps): React.JSX.Element;
8
9
  export {};
@@ -1,8 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import classnames from "classnames";
3
3
  import help_text_module from "./help-text.module.js";
4
- function HelpText({ className, size, text }) {
4
+ function HelpText({ id, className, size, text }) {
5
5
  return /*#__PURE__*/ jsx("p", {
6
+ id: id,
6
7
  className: classnames('byline-help-text', help_text_module.text, size && help_text_module[size], className),
7
8
  children: text
8
9
  });
@@ -29,11 +29,11 @@ const Input = ({ ref, id, name, type = 'text', variant = 'outlined', inputSize =
29
29
  required: required,
30
30
  autoComplete: autoComplete,
31
31
  placeholder: placeHolder,
32
- "aria-labelledby": `label-for-${id}`,
32
+ "aria-labelledby": null != label ? `label-for-${id}` : void 0,
33
33
  "aria-invalid": error,
34
34
  "aria-required": required,
35
35
  "aria-errormessage": errorText,
36
- "aria-describedby": error ? `error-for-${id}` : void 0,
36
+ "aria-describedby": error ? `error-for-${id}` : helpText.length > 0 ? `help-for-${id}` : void 0,
37
37
  className: classnames('byline-input', `byline-input-${variant}`, `byline-input-${inputSize}`, `byline-input-${intent}`, input_module.input, input_module[variant], input_module[inputSize], input_module[intent], {
38
38
  [input_module["start-adornment-padding"]]: null != startAdornment
39
39
  }, {
@@ -54,6 +54,7 @@ const Input = ({ ref, id, name, type = 'text', variant = 'outlined', inputSize =
54
54
  size: inputSize,
55
55
  text: errorText ?? helpText
56
56
  }) : helpText?.length > 0 && /*#__PURE__*/ jsx(HelpText, {
57
+ id: `help-for-${id}`,
57
58
  size: inputSize,
58
59
  text: helpText
59
60
  })
@@ -9,6 +9,7 @@ function Label({ className, id, htmlFor, label, required }) {
9
9
  children: [
10
10
  label,
11
11
  required && /*#__PURE__*/ jsx("span", {
12
+ "aria-hidden": "true",
12
13
  className: label_module.required,
13
14
  children: "\xa0*"
14
15
  })
@@ -23,11 +23,11 @@ const text_area_TextArea = function({ ref, id, name, label, rows = 4, required =
23
23
  rows: rows,
24
24
  autoComplete: autoComplete,
25
25
  placeholder: placeHolder,
26
- "aria-labelledby": `label-for-${id}`,
26
+ "aria-labelledby": null != label ? `label-for-${id}` : void 0,
27
27
  "aria-invalid": error,
28
28
  "aria-required": required,
29
29
  "aria-errormessage": errorText,
30
- "aria-describedby": error ? `error-for-${id}` : void 0,
30
+ "aria-describedby": error ? `error-for-${id}` : helpText.length > 0 ? `help-for-${id}` : void 0,
31
31
  className: classnames('byline-text-area', `byline-text-area-${variant}`, `byline-text-area-${intent}`, input_module.input, input_module[variant], input_module[intent], text_area_module["text-area"], text_area_module[variant], {
32
32
  [input_module.error]: error
33
33
  }, className),
@@ -37,6 +37,7 @@ const text_area_TextArea = function({ ref, id, name, label, rows = 4, required =
37
37
  id: `error-for-${id}`,
38
38
  text: errorText ?? helpText
39
39
  }) : helpText?.length > 0 && /*#__PURE__*/ jsx(HelpText, {
40
+ id: `help-for-${id}`,
40
41
  text: helpText
41
42
  })
42
43
  ]
@@ -630,7 +630,7 @@
630
630
  --stroke-noeffect: var(--gray-500);
631
631
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * .75) c h);
632
632
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * 1.5) calc(c * .8) h);
633
- --ring-noeffect: var(--gray-200);
633
+ --ring-noeffect: var(--gray-500);
634
634
  --gradient-noeffect-start: var(--gray-100);
635
635
  --gradient-noeffect-end: var(--gray-200);
636
636
  --gradient-noeffect-foreground: black;
@@ -891,7 +891,7 @@
891
891
  --stroke-noeffect: var(--gray-500);
892
892
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * 1.2) c h);
893
893
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * .8) calc(c * .8) h);
894
- --ring-noeffect: var(--gray-900);
894
+ --ring-noeffect: var(--gray-200);
895
895
  --gradient-noeffect-start: var(--gray-700);
896
896
  --gradient-noeffect-end: var(--gray-800);
897
897
  --gradient-noeffect-foreground: white;
@@ -1149,7 +1149,7 @@
1149
1149
  --stroke-noeffect: var(--gray-500);
1150
1150
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * .75) c h);
1151
1151
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * 1.5) calc(c * .8) h);
1152
- --ring-noeffect: var(--gray-200);
1152
+ --ring-noeffect: var(--gray-500);
1153
1153
  --gradient-noeffect-start: var(--gray-100);
1154
1154
  --gradient-noeffect-end: var(--gray-200);
1155
1155
  --gradient-noeffect-foreground: black;
@@ -6,6 +6,7 @@ export interface SearchProps extends React.InputHTMLAttributes<HTMLInputElement>
6
6
  inputClassName?: ClassName;
7
7
  intent?: Intent;
8
8
  className?: ClassName;
9
+ ariaLabel?: string;
9
10
  ariaLabelForSearch?: string;
10
11
  ariaLabelForClear?: string;
11
12
  onClear?: () => void;
@@ -17,4 +18,4 @@ export interface SearchProps extends React.InputHTMLAttributes<HTMLInputElement>
17
18
  };
18
19
  placeHolderText?: string;
19
20
  }
20
- export declare function Search({ variant, inputSize, inputClassName, intent, className, onClear, onEnter, onSearch, validatorFn, placeHolderText, ariaLabelForSearch, ariaLabelForClear, ...rest }: SearchProps): React.JSX.Element;
21
+ export declare function Search({ variant, inputSize, inputClassName, intent, className, onClear, onEnter, onSearch, validatorFn, placeHolderText, ariaLabel, ariaLabelForSearch, ariaLabelForClear, ...rest }: SearchProps): React.JSX.Element;
@@ -12,7 +12,7 @@ function fallbackSearchHandler(value) {
12
12
  else params.delete('query');
13
13
  window.location.search = params.toString();
14
14
  }
15
- function Search({ variant, inputSize, inputClassName, intent, className, onClear, onEnter, onSearch, validatorFn, placeHolderText = 'Search', ariaLabelForSearch = 'search', ariaLabelForClear = 'clear', ...rest }) {
15
+ function Search({ variant, inputSize, inputClassName, intent, className, onClear, onEnter, onSearch, validatorFn, placeHolderText = 'Search', ariaLabel, ariaLabelForSearch = 'Search', ariaLabelForClear = 'Clear search', ...rest }) {
16
16
  const [search, setSearch] = useState(null);
17
17
  const inputRef = useRef(null);
18
18
  const handleClear = ()=>{
@@ -64,20 +64,17 @@ function Search({ variant, inputSize, inputClassName, intent, className, onClear
64
64
  id: "search",
65
65
  defaultValue: search ?? '',
66
66
  name: "search",
67
+ "aria-label": ariaLabel ?? placeHolderText,
67
68
  placeHolder: placeHolderText,
68
69
  disabled: false,
69
70
  error: false,
70
71
  startAdornment: /*#__PURE__*/ jsx(InputAdornment, {
71
72
  position: "start",
72
73
  children: /*#__PURE__*/ jsx(IconButton, {
73
- role: "button",
74
74
  intent: "noeffect",
75
75
  variant: "text",
76
- style: {
77
- outline: 'none'
78
- },
79
76
  ripple: false,
80
- "arial-label": ariaLabelForSearch,
77
+ "aria-label": ariaLabelForSearch,
81
78
  size: "xs",
82
79
  onClick: ()=>{
83
80
  handleSearch();
@@ -91,13 +88,9 @@ function Search({ variant, inputSize, inputClassName, intent, className, onClear
91
88
  endAdornment: /*#__PURE__*/ jsx(InputAdornment, {
92
89
  position: "end",
93
90
  children: /*#__PURE__*/ jsx(IconButton, {
94
- role: "button",
95
91
  intent: "noeffect",
96
- style: {
97
- outline: 'none'
98
- },
99
92
  variant: "text",
100
- "arial-label": ariaLabelForClear,
93
+ "aria-label": ariaLabelForClear,
101
94
  size: "xs",
102
95
  onClick: ()=>{
103
96
  handleClear();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "3.15.2",
6
+ "version": "3.16.1",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -53,16 +53,13 @@
53
53
  "dependencies": {
54
54
  "@base-ui/react": "^1.5.0",
55
55
  "@dnd-kit/core": "^6.3.1",
56
- "@dnd-kit/modifiers": "^9.0.0",
57
56
  "@dnd-kit/sortable": "^10.0.0",
58
- "@dnd-kit/utilities": "^3.2.2",
59
57
  "classnames": "^2.5.1",
60
58
  "date-fns": "^4.2.1",
61
59
  "motion": "^12.40.0",
62
60
  "npm-run-all": "^4.1.5",
63
61
  "react-day-picker": "^10.0.1",
64
- "zod": "^4.4.3",
65
- "@byline/core": "3.15.2"
62
+ "zod": "^4.4.3"
66
63
  },
67
64
  "peerDependencies": {
68
65
  "react": "^19.0.0",
@@ -46,6 +46,14 @@
46
46
  outline-color: var(--ring-color);
47
47
  }
48
48
 
49
+ /* a11y (WCAG 2.4.7): show a focus ring on keyboard focus regardless of
50
+ checked state. The intent-level ring color is always defined, whereas
51
+ --ring-color is only set on [data-checked], so use it directly here. */
52
+ .checkbox:focus-visible,
53
+ :global(.byline-checkbox):focus-visible {
54
+ outline-color: var(--checkbox-variant-outline-ring-color);
55
+ }
56
+
49
57
  .checkbox[data-disabled],
50
58
  :global(.byline-checkbox)[data-disabled] {
51
59
  pointer-events: none;
@@ -5,13 +5,16 @@ import cx from 'classnames'
5
5
  import styles from './help-text.module.css'
6
6
 
7
7
  interface HelpTextProps {
8
+ id?: string
8
9
  className?: string
9
10
  size?: 'sm' | 'md' | 'lg'
10
11
  text: string
11
12
  }
12
13
 
13
- export function HelpText({ className, size, text }: HelpTextProps): React.JSX.Element {
14
+ export function HelpText({ id, className, size, text }: HelpTextProps): React.JSX.Element {
14
15
  return (
15
- <p className={cx('byline-help-text', styles.text, size && styles[size], className)}>{text}</p>
16
+ <p id={id} className={cx('byline-help-text', styles.text, size && styles[size], className)}>
17
+ {text}
18
+ </p>
16
19
  )
17
20
  }
@@ -76,11 +76,13 @@ export const Input = <_C extends React.ElementType = 'input'>({
76
76
  required={required}
77
77
  autoComplete={autoComplete}
78
78
  placeholder={placeHolder}
79
- aria-labelledby={`label-for-${id}`}
79
+ aria-labelledby={label != null ? `label-for-${id}` : undefined}
80
80
  aria-invalid={error}
81
81
  aria-required={required}
82
82
  aria-errormessage={errorText}
83
- aria-describedby={error ? `error-for-${id}` : undefined}
83
+ aria-describedby={
84
+ error ? `error-for-${id}` : helpText.length > 0 ? `help-for-${id}` : undefined
85
+ }
84
86
  className={cx(
85
87
  'byline-input',
86
88
  `byline-input-${variant}`,
@@ -108,7 +110,7 @@ export const Input = <_C extends React.ElementType = 'input'>({
108
110
  {error ? (
109
111
  <ErrorText id={`error-for-${id}`} size={inputSize} text={errorText ?? helpText} />
110
112
  ) : (
111
- helpText?.length > 0 && <HelpText size={inputSize} text={helpText} />
113
+ helpText?.length > 0 && <HelpText id={`help-for-${id}`} size={inputSize} text={helpText} />
112
114
  )}
113
115
  </div>
114
116
  )
@@ -20,7 +20,14 @@ export function Label({ className, id, htmlFor, label, required }: LabelProps):
20
20
  className={cx('label', styles.label, className)}
21
21
  >
22
22
  {label}
23
- {required && <span className={styles.required}>&nbsp;*</span>}
23
+ {/* a11y: the input carries aria-required, so the visual asterisk is
24
+ decorative — hide it from the accessible name (it would otherwise be
25
+ read as part of the label via aria-labelledby). */}
26
+ {required && (
27
+ <span aria-hidden="true" className={styles.required}>
28
+ &nbsp;*
29
+ </span>
30
+ )}
24
31
  </label>
25
32
  )
26
33
  }
@@ -54,11 +54,13 @@ export const TextArea = function TextArea({
54
54
  rows={rows}
55
55
  autoComplete={autoComplete}
56
56
  placeholder={placeHolder}
57
- aria-labelledby={`label-for-${id}`}
57
+ aria-labelledby={label != null ? `label-for-${id}` : undefined}
58
58
  aria-invalid={error}
59
59
  aria-required={required}
60
60
  aria-errormessage={errorText}
61
- aria-describedby={error ? `error-for-${id}` : undefined}
61
+ aria-describedby={
62
+ error ? `error-for-${id}` : helpText.length > 0 ? `help-for-${id}` : undefined
63
+ }
62
64
  className={cx(
63
65
  'byline-text-area',
64
66
  `byline-text-area-${variant}`,
@@ -76,7 +78,7 @@ export const TextArea = function TextArea({
76
78
  {error ? (
77
79
  <ErrorText id={`error-for-${id}`} text={errorText ?? helpText} />
78
80
  ) : (
79
- helpText?.length > 0 && <HelpText text={helpText} />
81
+ helpText?.length > 0 && <HelpText id={`help-for-${id}`} text={helpText} />
80
82
  )}
81
83
  </div>
82
84
  )
@@ -210,7 +210,9 @@
210
210
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * 0.75) c h);
211
211
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * 1.5) calc(c * 0.8) h);
212
212
 
213
- --ring-noeffect: var(--gray-200);
213
+ /* a11y: gray-200 (#C0C0C0) is only 1.8:1 on white; gray-500 (#666666) is
214
+ 5.78:1, clearing the WCAG 1.4.11 / 2.4.7 3:1 non-text contrast minimum. */
215
+ --ring-noeffect: var(--gray-500);
214
216
 
215
217
  --gradient-noeffect-start: var(--gray-100);
216
218
  --gradient-noeffect-end: var(--gray-200);
@@ -637,7 +639,9 @@
637
639
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * 1.2) c h);
638
640
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * 0.8) calc(c * 0.8) h);
639
641
 
640
- --ring-noeffect: var(--gray-900);
642
+ /* a11y: a dark ring (gray-900) is ~1.1:1 on the dark canvas — effectively
643
+ invisible. A light ring (gray-200) gives 5.5–10:1 on dark surfaces. */
644
+ --ring-noeffect: var(--gray-200);
641
645
 
642
646
  --gradient-noeffect-start: var(--gray-700);
643
647
  --gradient-noeffect-end: var(--gray-800);
@@ -1051,7 +1055,9 @@
1051
1055
  --stroke-noeffect-hover: oklch(from var(--gray-500) calc(l * 0.75) c h);
1052
1056
  --stroke-noeffect-disabled: oklch(from var(--gray-500) calc(l * 1.5) calc(c * 0.8) h);
1053
1057
 
1054
- --ring-noeffect: var(--gray-200);
1058
+ /* a11y: gray-200 (#C0C0C0) is only 1.8:1 on white; gray-500 (#666666) is
1059
+ 5.78:1, clearing the WCAG 1.4.11 / 2.4.7 3:1 non-text contrast minimum. */
1060
+ --ring-noeffect: var(--gray-500);
1055
1061
 
1056
1062
  --gradient-noeffect-start: var(--gray-100);
1057
1063
  --gradient-noeffect-end: var(--gray-200);
@@ -15,6 +15,7 @@ export interface SearchProps extends React.InputHTMLAttributes<HTMLInputElement>
15
15
  inputClassName?: ClassName
16
16
  intent?: Intent
17
17
  className?: ClassName
18
+ ariaLabel?: string
18
19
  ariaLabelForSearch?: string
19
20
  ariaLabelForClear?: string
20
21
  onClear?: () => void
@@ -52,8 +53,9 @@ export function Search({
52
53
  onSearch,
53
54
  validatorFn,
54
55
  placeHolderText = 'Search',
55
- ariaLabelForSearch = 'search',
56
- ariaLabelForClear = 'clear',
56
+ ariaLabel,
57
+ ariaLabelForSearch = 'Search',
58
+ ariaLabelForClear = 'Clear search',
57
59
  ...rest
58
60
  }: SearchProps): React.JSX.Element {
59
61
  const [search, setSearch] = useState<null | string>(null)
@@ -144,18 +146,17 @@ export function Search({
144
146
  id="search"
145
147
  defaultValue={search ?? ''}
146
148
  name="search"
149
+ aria-label={ariaLabel ?? placeHolderText}
147
150
  placeHolder={placeHolderText}
148
151
  disabled={false}
149
152
  error={false}
150
153
  startAdornment={
151
154
  <InputAdornment position="start">
152
155
  <IconButton
153
- role="button"
154
156
  intent="noeffect"
155
157
  variant="text"
156
- style={{ outline: 'none' }}
157
158
  ripple={false}
158
- arial-label={ariaLabelForSearch}
159
+ aria-label={ariaLabelForSearch}
159
160
  size="xs"
160
161
  onClick={() => {
161
162
  handleSearch()
@@ -168,11 +169,9 @@ export function Search({
168
169
  endAdornment={
169
170
  <InputAdornment position="end">
170
171
  <IconButton
171
- role="button"
172
172
  intent="noeffect"
173
- style={{ outline: 'none' }}
174
173
  variant="text"
175
- arial-label={ariaLabelForClear}
174
+ aria-label={ariaLabelForClear}
176
175
  size="xs"
177
176
  onClick={() => {
178
177
  handleClear()