@alexandregme/zljs 1.10.0 → 1.12.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,63 @@ A change that needs action in an app has a **Migration** note. When updating, re
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.12.0] - 2026-11-02
12
+
13
+ ### Added
14
+
15
+ - `TextArea` takes `aria-label`, `name`, `autoComplete`, `required`, `onBlur`, `onKeyDown` and `ref`, as `Input` and `Select` do, so it works with `register` from `react-hook-form`
16
+
17
+ ### Changed
18
+
19
+ - `TextArea` requires `label` or `aria-label` in its type, instead of always `label`
20
+ - `TextArea` draws a real `focus-visible` outline in the theme's `primary` color instead of a `focus:ring`, so focus stays visible in forced colors mode, and an app's own color reaches it
21
+ - `TextArea` border is now `gray-500` instead of `gray-300`, for 3:1 contrast, and its placeholder is `gray-500`, for 4.5:1
22
+
23
+ ### Fixed
24
+
25
+ - `TextArea` with `error` sets `aria-invalid` and points `aria-describedby` to the message, so a screen reader announces both
26
+ - `TextArea` uses `resize-y`. It had a class that doesn't exist in Tailwind, so it only resized vertically in apps with Tailwind's base styles
27
+
28
+ #### Migration
29
+
30
+ Only code that extends `TextAreaProps` and tests that assert its old classes need a change. To find them, run the commands below from the app's root:
31
+
32
+ ```sh
33
+ grep -rnE "\bTextAreaProps\b" . --include='*.ts' --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next
34
+ grep -rnE "border-gray-300|focus:ring-blue-500|focus:border-transparent|resize-vertical" . --include='*.test.*' --exclude-dir=node_modules --exclude-dir=.next
35
+ ```
36
+
37
+ Replace `interface X extends TextAreaProps` with `type X = TextAreaProps & { ... }`, because `TextAreaProps` is now a union. In tests, replace those classes with `focus-visible:outline-zl-primary`, `border-gray-500` and `resize-y`.
38
+
39
+ ## [1.11.0] - 2026-11-02
40
+
41
+ ### Added
42
+
43
+ - `Select` takes `aria-label`, `name`, `autoComplete`, `required`, `onBlur` and `onKeyDown`, and forwards `ref`, as `Input` does, so it works with `register` from `react-hook-form`
44
+
45
+ ### Changed
46
+
47
+ - `Select` requires `label` or `aria-label` in its type, instead of always `label`
48
+ - `Select` draws a real `focus-visible` outline in the theme's `primary` color instead of a `focus:ring`, so focus stays visible in forced colors mode, and an app's own color reaches it
49
+ - `Select` border is now `gray-500` instead of `gray-300`, for 3:1 contrast
50
+
51
+ ### Fixed
52
+
53
+ - `Select` shows its `placeholder` when there is no `value`. It used to start on the first option, so the `placeholder` never showed
54
+ - `Select` with `error` sets `aria-invalid` and points `aria-describedby` to the message, so a screen reader announces both
55
+
56
+ #### Migration
57
+
58
+ Only a `Select` with a `placeholder` and no `value`, code that extends `SelectProps`, and tests that assert its old classes need a change. To find them, run the commands below from the app's root. The first also finds other components named `Select`, and those stay as they are:
59
+
60
+ ```sh
61
+ grep -rnE -A6 "<Select([[:space:]/>]|$)" . --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next | grep -E "placeholder"
62
+ grep -rnE "\bSelectProps\b" . --include='*.ts' --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next
63
+ grep -rnE "border-gray-300|focus:ring-blue-500|focus:border-transparent" . --include='*.test.*' --exclude-dir=node_modules --exclude-dir=.next
64
+ ```
65
+
66
+ That `Select` now starts empty, so `onChange` runs only after the user picks an option. If the app counted on the first option being selected, pass it as `value`. Replace `interface X extends SelectProps` with `type X = SelectProps & { ... }`, because `SelectProps` is now a union. In tests, replace those classes with `focus-visible:outline-zl-primary` and `border-gray-500`.
67
+
11
68
  ## [1.10.0] - 2026-11-02
12
69
 
13
70
  ### Added
@@ -77,7 +134,9 @@ Replace the shade with the class of the `color` being tested, for example `/(^|\
77
134
 
78
135
  The message of `Error` is `text-zl-danger`. `none` and `ghost` didn't change.
79
136
 
80
- [Unreleased]: https://github.com/alexandregme/zljs/compare/v1.10.0...HEAD
137
+ [Unreleased]: https://github.com/alexandregme/zljs/compare/v1.12.0...HEAD
138
+ [1.12.0]: https://github.com/alexandregme/zljs/compare/v1.11.0...v1.12.0
139
+ [1.11.0]: https://github.com/alexandregme/zljs/compare/v1.10.0...v1.11.0
81
140
  [1.10.0]: https://github.com/alexandregme/zljs/compare/v1.9.0...v1.10.0
82
141
  [1.9.0]: https://github.com/alexandregme/zljs/compare/v1.8.0...v1.9.0
83
142
  [1.8.0]: https://github.com/alexandregme/zljs/compare/v1.7.0...v1.8.0
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ChangeEvent, FormEvent, HTMLInputAutoCompleteAttribute, FocusEvent, KeyboardEvent } from 'react';
2
+ import { ReactNode, ChangeEvent, FormEvent, HTMLInputAutoCompleteAttribute, FocusEvent, KeyboardEvent, Ref } from 'react';
3
3
  export { FormInput, FormInputProps, FormProvider, FormProviderProps } from './rhf/index.js';
4
4
  export { toast } from 'sonner';
5
5
  import * as zustand from 'zustand';
@@ -136,29 +136,53 @@ interface SelectOption {
136
136
  value: string;
137
137
  label: string;
138
138
  }
139
- interface SelectProps {
139
+ type SelectName = {
140
140
  label: string;
141
+ "aria-label"?: string;
142
+ } | {
143
+ label?: undefined;
144
+ "aria-label": string;
145
+ };
146
+ type SelectProps = SelectName & {
147
+ name?: string;
141
148
  options: SelectOption[];
142
- value?: string;
143
149
  placeholder?: string;
144
- disabled?: boolean;
150
+ autoComplete?: HTMLInputAutoCompleteAttribute;
151
+ value?: string;
145
152
  error?: string;
153
+ disabled?: boolean;
154
+ required?: boolean;
146
155
  onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;
147
- }
156
+ onBlur?: (event: FocusEvent<HTMLSelectElement>) => void;
157
+ onKeyDown?: (event: KeyboardEvent<HTMLSelectElement>) => void;
158
+ ref?: Ref<HTMLSelectElement>;
159
+ };
148
160
 
149
- declare const Select: ({ label: labelText, options, value, placeholder, disabled, error, onChange, }: SelectProps) => react.JSX.Element;
161
+ declare const Select: ({ label: labelText, "aria-label": ariaLabel, name, options, placeholder, autoComplete, value, error, disabled, required, onChange, onBlur, onKeyDown, ref, }: SelectProps) => react.JSX.Element;
150
162
 
151
- interface TextAreaProps {
163
+ type TextAreaName = {
152
164
  label: string;
165
+ "aria-label"?: string;
166
+ } | {
167
+ label?: undefined;
168
+ "aria-label": string;
169
+ };
170
+ type TextAreaProps = TextAreaName & {
171
+ name?: string;
153
172
  placeholder?: string;
173
+ autoComplete?: HTMLInputAutoCompleteAttribute;
154
174
  value?: string;
155
175
  rows?: number;
156
176
  error?: string;
157
177
  disabled?: boolean;
178
+ required?: boolean;
158
179
  onChange?: (event: ChangeEvent<HTMLTextAreaElement>) => void;
159
- }
180
+ onBlur?: (event: FocusEvent<HTMLTextAreaElement>) => void;
181
+ onKeyDown?: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
182
+ ref?: Ref<HTMLTextAreaElement>;
183
+ };
160
184
 
161
- declare const TextArea: ({ label: labelText, placeholder, value, rows, error, disabled, onChange, }: TextAreaProps) => react.JSX.Element;
185
+ declare const TextArea: ({ label: labelText, "aria-label": ariaLabel, name, placeholder, autoComplete, value, rows, error, disabled, required, onChange, onBlur, onKeyDown, ref, }: TextAreaProps) => react.JSX.Element;
162
186
 
163
187
  declare const Icon: ({ name, size, "aria-label": ariaLabel, }: IconProps) => react.JSX.Element;
164
188
 
package/dist/index.js CHANGED
@@ -356,30 +356,48 @@ var label3 = tv5({
356
356
  base: "text-sm font-medium text-gray-700"
357
357
  });
358
358
  var select = tv5({
359
- base: "px-3 py-2 border border-gray-300 rounded-md text-sm bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
359
+ base: "px-3 py-2 border border-gray-500 rounded-md text-sm bg-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zl-primary disabled:bg-gray-100 disabled:cursor-not-allowed"
360
360
  });
361
361
 
362
362
  // core/forms/select/select.tsx
363
363
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
364
364
  var Select = ({
365
365
  label: labelText,
366
+ "aria-label": ariaLabel,
367
+ name,
366
368
  options,
367
- value,
368
369
  placeholder,
369
- disabled,
370
+ autoComplete,
371
+ value,
370
372
  error,
371
- onChange
373
+ disabled,
374
+ required,
375
+ onChange,
376
+ onBlur,
377
+ onKeyDown,
378
+ ref
372
379
  }) => {
373
380
  const id = useId3();
381
+ const errorId = `${id}-error`;
374
382
  return /* @__PURE__ */ jsxs4("div", { className: wrapper3(), children: [
375
- /* @__PURE__ */ jsx6("label", { htmlFor: id, className: label3(), children: labelText }),
383
+ labelText && /* @__PURE__ */ jsx6("label", { htmlFor: id, className: label3(), children: labelText }),
376
384
  /* @__PURE__ */ jsxs4(
377
385
  "select",
378
386
  {
387
+ ref,
379
388
  id,
389
+ name,
390
+ autoComplete,
380
391
  value,
392
+ defaultValue: placeholder && value === void 0 ? "" : void 0,
381
393
  disabled,
394
+ required,
382
395
  onChange,
396
+ onBlur,
397
+ onKeyDown,
398
+ "aria-label": ariaLabel,
399
+ "aria-invalid": error ? true : void 0,
400
+ "aria-describedby": error ? errorId : void 0,
383
401
  className: select(),
384
402
  children: [
385
403
  placeholder && /* @__PURE__ */ jsx6("option", { value: "", disabled: true, children: placeholder }),
@@ -387,7 +405,7 @@ var Select = ({
387
405
  ]
388
406
  }
389
407
  ),
390
- error && /* @__PURE__ */ jsx6(Error, { message: error })
408
+ error && /* @__PURE__ */ jsx6(Error, { id: errorId, message: error })
391
409
  ] });
392
410
  };
393
411
 
@@ -403,36 +421,53 @@ var label4 = tv6({
403
421
  base: "text-sm font-medium text-gray-700"
404
422
  });
405
423
  var textarea = tv6({
406
- base: "px-3 py-2 border border-gray-300 rounded-md text-sm resize-vertical focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
424
+ base: "px-3 py-2 border border-gray-500 rounded-md text-sm placeholder:text-gray-500 resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zl-primary disabled:bg-gray-100 disabled:cursor-not-allowed"
407
425
  });
408
426
 
409
427
  // core/forms/text-area/text-area.tsx
410
428
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
411
429
  var TextArea = ({
412
430
  label: labelText,
431
+ "aria-label": ariaLabel,
432
+ name,
413
433
  placeholder,
434
+ autoComplete,
414
435
  value,
415
436
  rows = 3,
416
437
  error,
417
438
  disabled,
418
- onChange
439
+ required,
440
+ onChange,
441
+ onBlur,
442
+ onKeyDown,
443
+ ref
419
444
  }) => {
420
445
  const id = useId4();
446
+ const errorId = `${id}-error`;
421
447
  return /* @__PURE__ */ jsxs5("div", { className: wrapper4(), children: [
422
- /* @__PURE__ */ jsx7("label", { htmlFor: id, className: label4(), children: labelText }),
448
+ labelText && /* @__PURE__ */ jsx7("label", { htmlFor: id, className: label4(), children: labelText }),
423
449
  /* @__PURE__ */ jsx7(
424
450
  "textarea",
425
451
  {
452
+ ref,
426
453
  id,
454
+ name,
427
455
  placeholder,
456
+ autoComplete,
428
457
  value,
429
458
  rows,
430
459
  disabled,
460
+ required,
431
461
  onChange,
462
+ onBlur,
463
+ onKeyDown,
464
+ "aria-label": ariaLabel,
465
+ "aria-invalid": error ? true : void 0,
466
+ "aria-describedby": error ? errorId : void 0,
432
467
  className: textarea()
433
468
  }
434
469
  ),
435
- error && /* @__PURE__ */ jsx7(Error, { message: error })
470
+ error && /* @__PURE__ */ jsx7(Error, { id: errorId, message: error })
436
471
  ] });
437
472
  };
438
473
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexandregme/zljs",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Opinionated, accessible React component library for enterprise apps (Next.js, Tailwind CSS v4).",
5
5
  "keywords": [
6
6
  "react",