@alexandregme/zljs 1.10.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,35 @@ 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
+
11
40
  ## [1.10.0] - 2026-11-02
12
41
 
13
42
  ### Added
@@ -77,7 +106,8 @@ Replace the shade with the class of the `color` being tested, for example `/(^|\
77
106
 
78
107
  The message of `Error` is `text-zl-danger`. `none` and `ghost` didn't change.
79
108
 
80
- [Unreleased]: https://github.com/alexandregme/zljs/compare/v1.10.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
81
111
  [1.10.0]: https://github.com/alexandregme/zljs/compare/v1.9.0...v1.10.0
82
112
  [1.9.0]: https://github.com/alexandregme/zljs/compare/v1.8.0...v1.9.0
83
113
  [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,17 +136,29 @@ 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
163
  interface TextAreaProps {
152
164
  label: string;
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexandregme/zljs",
3
- "version": "1.10.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",