@alexandregme/zljs 1.9.0 → 1.11.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,64 @@ A change that needs action in an app has a **Migration** note. When updating, re
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.11.0] - 2026-11-02
12
+
13
+ ### Added
14
+
15
+ - `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`
16
+
17
+ ### Changed
18
+
19
+ - `Select` requires `label` or `aria-label` in its type, instead of always `label`
20
+ - `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
21
+ - `Select` border is now `gray-500` instead of `gray-300`, for 3:1 contrast
22
+
23
+ ### Fixed
24
+
25
+ - `Select` shows its `placeholder` when there is no `value`. It used to start on the first option, so the `placeholder` never showed
26
+ - `Select` with `error` sets `aria-invalid` and points `aria-describedby` to the message, so a screen reader announces both
27
+
28
+ #### Migration
29
+
30
+ 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:
31
+
32
+ ```sh
33
+ grep -rnE -A6 "<Select([[:space:]/>]|$)" . --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next | grep -E "placeholder"
34
+ grep -rnE "\bSelectProps\b" . --include='*.ts' --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next
35
+ grep -rnE "border-gray-300|focus:ring-blue-500|focus:border-transparent" . --include='*.test.*' --exclude-dir=node_modules --exclude-dir=.next
36
+ ```
37
+
38
+ 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`.
39
+
40
+ ## [1.10.0] - 2026-11-02
41
+
42
+ ### Added
43
+
44
+ - `Input` takes `autoComplete` and `required`
45
+
46
+ ### Changed
47
+
48
+ - `Input` requires `label` or `aria-label` in its type, so a field can't be left without a name
49
+ - `Input` 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
50
+ - `Input` border is now `gray-500` instead of `gray-300`, for 3:1 contrast, and its placeholder is `gray-500`, for 4.5:1
51
+
52
+ ### Fixed
53
+
54
+ - `Input` with `error` sets `aria-invalid` and points `aria-describedby` to the message, so a screen reader announces both
55
+
56
+ #### Migration
57
+
58
+ Only an `Input` with neither `label` nor `aria-label`, code that extends `InputProps`, 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 `Input`, and those stay as they are:
59
+
60
+ ```sh
61
+ grep -rnE "<Input([[:space:]/>]|$)|\bInputProps\b" . --include='*.ts' --include='*.tsx' --exclude-dir=node_modules --exclude-dir=.next
62
+ grep -rnE "border-gray-300|focus:ring-blue-500|focus:border-transparent" . --include='*.test.*' --exclude-dir=node_modules --exclude-dir=.next
63
+ ```
64
+
65
+ Give each field a `label`, or an `aria-label` when the context already says what it is. Replace `interface X extends InputProps` with `type X = InputProps & { ... }`, because `InputProps` is now a union. In tests, replace those classes with `focus-visible:outline-zl-primary` and `border-gray-500`.
66
+
67
+ `FormInput` already requires `label`, so it doesn't change.
68
+
11
69
  ## [1.9.0] - 2026-11-02
12
70
 
13
71
  ### Added
@@ -48,6 +106,8 @@ Replace the shade with the class of the `color` being tested, for example `/(^|\
48
106
 
49
107
  The message of `Error` is `text-zl-danger`. `none` and `ghost` didn't change.
50
108
 
51
- [Unreleased]: https://github.com/alexandregme/zljs/compare/v1.9.0...HEAD
109
+ [Unreleased]: https://github.com/alexandregme/zljs/compare/v1.11.0...HEAD
110
+ [1.11.0]: https://github.com/alexandregme/zljs/compare/v1.10.0...v1.11.0
111
+ [1.10.0]: https://github.com/alexandregme/zljs/compare/v1.9.0...v1.10.0
52
112
  [1.9.0]: https://github.com/alexandregme/zljs/compare/v1.8.0...v1.9.0
53
113
  [1.8.0]: https://github.com/alexandregme/zljs/compare/v1.7.0...v1.8.0
@@ -49,7 +49,7 @@ var label = tv2({
49
49
  base: "text-sm font-medium text-gray-700"
50
50
  });
51
51
  var input = tv2({
52
- base: "px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
52
+ base: "px-3 py-2 border border-gray-500 rounded-md text-sm placeholder:text-gray-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-zl-primary disabled:bg-gray-100 disabled:cursor-not-allowed"
53
53
  });
54
54
 
55
55
  // core/forms/input/input.tsx
@@ -61,14 +61,17 @@ var Input = forwardRef(
61
61
  name,
62
62
  type = "text",
63
63
  placeholder,
64
+ autoComplete,
64
65
  value,
65
66
  error: error2,
66
67
  disabled,
68
+ required,
67
69
  onChange,
68
70
  onBlur,
69
71
  onKeyDown
70
72
  }, ref) => {
71
73
  const id = useId();
74
+ const errorId = `${id}-error`;
72
75
  return /* @__PURE__ */ jsxs("div", { className: wrapper(), children: [
73
76
  labelText && /* @__PURE__ */ jsx3("label", { htmlFor: id, className: label(), children: labelText }),
74
77
  /* @__PURE__ */ jsx3(
@@ -79,16 +82,20 @@ var Input = forwardRef(
79
82
  name,
80
83
  type,
81
84
  placeholder,
85
+ autoComplete,
82
86
  value,
83
87
  disabled,
88
+ required,
84
89
  onChange,
85
90
  onBlur,
86
91
  onKeyDown,
87
92
  "aria-label": ariaLabel,
93
+ "aria-invalid": error2 ? true : void 0,
94
+ "aria-describedby": error2 ? errorId : void 0,
88
95
  className: input()
89
96
  }
90
97
  ),
91
- error2 && /* @__PURE__ */ jsx3(Error, { message: error2 })
98
+ error2 && /* @__PURE__ */ jsx3(Error, { id: errorId, message: error2 })
92
99
  ] });
93
100
  }
94
101
  );
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ChangeEvent, FormEvent, 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';
@@ -98,19 +98,26 @@ interface FormProps {
98
98
 
99
99
  declare const Form: ({ children, onSubmit, disabled }: FormProps) => react.JSX.Element;
100
100
 
101
- interface InputProps {
102
- label?: string;
101
+ type InputName = {
102
+ label: string;
103
103
  "aria-label"?: string;
104
+ } | {
105
+ label?: undefined;
106
+ "aria-label": string;
107
+ };
108
+ type InputProps = InputName & {
104
109
  name?: string;
105
110
  type?: "text" | "password" | "email";
106
111
  placeholder?: string;
112
+ autoComplete?: HTMLInputAutoCompleteAttribute;
107
113
  value?: string;
108
114
  error?: string;
109
115
  disabled?: boolean;
116
+ required?: boolean;
110
117
  onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
111
118
  onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
112
119
  onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
113
- }
120
+ };
114
121
 
115
122
  declare const Input: react.ForwardRefExoticComponent<InputProps & react.RefAttributes<HTMLInputElement>>;
116
123
 
@@ -129,17 +136,29 @@ interface SelectOption {
129
136
  value: string;
130
137
  label: string;
131
138
  }
132
- interface SelectProps {
139
+ type SelectName = {
133
140
  label: string;
141
+ "aria-label"?: string;
142
+ } | {
143
+ label?: undefined;
144
+ "aria-label": string;
145
+ };
146
+ type SelectProps = SelectName & {
147
+ name?: string;
134
148
  options: SelectOption[];
135
- value?: string;
136
149
  placeholder?: string;
137
- disabled?: boolean;
150
+ autoComplete?: HTMLInputAutoCompleteAttribute;
151
+ value?: string;
138
152
  error?: string;
153
+ disabled?: boolean;
154
+ required?: boolean;
139
155
  onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;
140
- }
156
+ onBlur?: (event: FocusEvent<HTMLSelectElement>) => void;
157
+ onKeyDown?: (event: KeyboardEvent<HTMLSelectElement>) => void;
158
+ ref?: Ref<HTMLSelectElement>;
159
+ };
141
160
 
142
- 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;
143
162
 
144
163
  interface TextAreaProps {
145
164
  label: string;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  FormInput,
5
5
  FormProvider,
6
6
  Input
7
- } from "./chunk-WZNUKE6S.js";
7
+ } from "./chunk-2RTPRD3E.js";
8
8
 
9
9
  // core/icon/icon.tsx
10
10
  import * as LucideIcons from "lucide-react";
@@ -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
 
package/dist/rhf/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  FormInput,
4
4
  FormProvider
5
- } from "../chunk-WZNUKE6S.js";
5
+ } from "../chunk-2RTPRD3E.js";
6
6
  export {
7
7
  FormInput,
8
8
  FormProvider
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexandregme/zljs",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Opinionated, accessible React component library for enterprise apps (Next.js, Tailwind CSS v4).",
5
5
  "keywords": [
6
6
  "react",