prime-ui-kit 0.21.3 → 0.21.4
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/dist/chunks/{chunk-JO7E6SZQ.js → chunk-PFASPQSJ.js} +365 -337
- package/dist/chunks/{chunk-R5LI5BO3.js → chunk-UNNQITFE.js} +11 -6
- package/dist/chunks/{chunk-BAJRN3QN.js → chunk-WGXXPJDR.js} +4 -1
- package/dist/color-picker.js +2 -2
- package/dist/components/input/Input.d.ts +1 -1
- package/dist/components/login-form/LoginForm.d.ts +5 -1
- package/dist/components/textarea/Textarea.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/autofill.d.ts +30 -0
- package/dist/skill/checklist.md +2 -0
- package/dist/skill/reference/components/button/examples/in-form.tsx +0 -1
- package/dist/skill/reference/components/drawer/examples/screen.tsx +1 -5
- package/dist/skill/reference/components/input/COMPONENT.md +3 -0
- package/dist/skill/reference/components/input/examples/in-form.tsx +0 -1
- package/dist/skill/reference/components/input/examples/validation.tsx +1 -1
- package/dist/skill/reference/components/login-form/COMPONENT.md +2 -2
- package/dist/skill/reference/components/login-form/examples/sizes.tsx +6 -2
- package/dist/skill/reference/components/tabs/examples/orientation.tsx +1 -1
- package/dist/skill/reference/components/textarea/COMPONENT.md +2 -0
- package/dist/skill/ux.md +19 -2
- package/package.json +1 -1
- package/src/components/button/examples/in-form.tsx +0 -1
- package/src/components/drawer/examples/screen.tsx +1 -5
- package/src/components/input/COMPONENT.md +3 -0
- package/src/components/input/examples/in-form.tsx +0 -1
- package/src/components/input/examples/validation.tsx +1 -1
- package/src/components/login-form/COMPONENT.md +2 -2
- package/src/components/login-form/examples/sizes.tsx +6 -2
- package/src/components/tabs/examples/orientation.tsx +1 -1
- package/src/components/textarea/COMPONENT.md +2 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AUTOFILL_OFF,
|
|
4
5
|
AnchorRectProvider,
|
|
6
|
+
AutofillScope,
|
|
5
7
|
Avatar,
|
|
6
8
|
Badge,
|
|
7
9
|
Button,
|
|
@@ -138,6 +140,7 @@ import {
|
|
|
138
140
|
touchTargetClass,
|
|
139
141
|
touchTargetRowClass,
|
|
140
142
|
useAnchorName,
|
|
143
|
+
useAutofill,
|
|
141
144
|
useBalancedColumns,
|
|
142
145
|
useChipTier,
|
|
143
146
|
useControllableState,
|
|
@@ -183,7 +186,7 @@ import {
|
|
|
183
186
|
useTextParts,
|
|
184
187
|
visuallyHiddenClass,
|
|
185
188
|
zonedParts
|
|
186
|
-
} from "./chunk-
|
|
189
|
+
} from "./chunk-PFASPQSJ.js";
|
|
187
190
|
import {
|
|
188
191
|
COMPACT_QUERY,
|
|
189
192
|
ControlSizeProvider,
|
|
@@ -2969,6 +2972,7 @@ function FooterField({
|
|
|
2969
2972
|
className: cx(fieldInputTextClass, Datepicker_default.fieldTime),
|
|
2970
2973
|
value: time,
|
|
2971
2974
|
inputMode: "numeric",
|
|
2975
|
+
...AUTOFILL_OFF,
|
|
2972
2976
|
maxLength: 5,
|
|
2973
2977
|
onChange: (e) => onTime(e.target.value)
|
|
2974
2978
|
}
|
|
@@ -3850,7 +3854,6 @@ function SmartFilterToolbar({
|
|
|
3850
3854
|
Input.Field,
|
|
3851
3855
|
{
|
|
3852
3856
|
type: "search",
|
|
3853
|
-
autoComplete: "off",
|
|
3854
3857
|
"aria-label": labels.searchPlaceholder,
|
|
3855
3858
|
placeholder: labels.searchPlaceholder,
|
|
3856
3859
|
value: search,
|
|
@@ -4103,7 +4106,6 @@ function NumberRange({
|
|
|
4103
4106
|
Input.Field,
|
|
4104
4107
|
{
|
|
4105
4108
|
inputMode: "decimal",
|
|
4106
|
-
autoComplete: "off",
|
|
4107
4109
|
"aria-label": formatLabel(side === "from" ? labels.fromLabel : labels.toLabel, {
|
|
4108
4110
|
field: field.label
|
|
4109
4111
|
}),
|
|
@@ -19935,7 +19937,6 @@ function CustomColorField() {
|
|
|
19935
19937
|
{
|
|
19936
19938
|
value: draft,
|
|
19937
19939
|
placeholder: "#12A594",
|
|
19938
|
-
autoComplete: "off",
|
|
19939
19940
|
spellCheck: false,
|
|
19940
19941
|
onValueChange: (next) => {
|
|
19941
19942
|
setDraft(next);
|
|
@@ -20237,7 +20238,7 @@ function CommandMenuInput({
|
|
|
20237
20238
|
"aria-controls": listboxId,
|
|
20238
20239
|
"aria-activedescendant": activeId ? optionId(activeId) : void 0,
|
|
20239
20240
|
"aria-busy": loading || loadingMore || void 0,
|
|
20240
|
-
|
|
20241
|
+
...AUTOFILL_OFF,
|
|
20241
20242
|
autoCorrect: "off",
|
|
20242
20243
|
spellCheck: false,
|
|
20243
20244
|
"data-autofocus": "",
|
|
@@ -21982,6 +21983,7 @@ function TextareaRoot({
|
|
|
21982
21983
|
disabled: disabledProp,
|
|
21983
21984
|
readOnly,
|
|
21984
21985
|
value,
|
|
21986
|
+
autoComplete,
|
|
21985
21987
|
onInput,
|
|
21986
21988
|
onChange,
|
|
21987
21989
|
onValueChange,
|
|
@@ -22002,6 +22004,7 @@ function TextareaRoot({
|
|
|
22002
22004
|
const disabled = useInheritedDisabled(disabledProp);
|
|
22003
22005
|
const ids = useFieldFrame(id, { label, hint, error, invalid, required }, ariaDescribedBy);
|
|
22004
22006
|
const labels = useLabels(TEXTAREA_LABELS, labelsProp);
|
|
22007
|
+
const autofill = useAutofill(autoComplete);
|
|
22005
22008
|
const innerRef = React67.useRef(null);
|
|
22006
22009
|
const setRefs = useMergedRefs(innerRef, ref);
|
|
22007
22010
|
const mirrorRef = React67.useRef(null);
|
|
@@ -22061,6 +22064,7 @@ function TextareaRoot({
|
|
|
22061
22064
|
"aria-invalid": ids.invalid || void 0,
|
|
22062
22065
|
"aria-describedby": ids.describedBy,
|
|
22063
22066
|
"aria-busy": commit.state === "pending" || void 0,
|
|
22067
|
+
...autofill,
|
|
22064
22068
|
value,
|
|
22065
22069
|
onInput: (event) => {
|
|
22066
22070
|
if (autoResize && mirrorRef.current) {
|
|
@@ -30161,7 +30165,7 @@ function LoginFormBody({ className, children, ...rest }) {
|
|
|
30161
30165
|
}
|
|
30162
30166
|
LoginFormBody.displayName = "LoginForm.Body";
|
|
30163
30167
|
function LoginFormForm({ className, children, ref, ...rest }) {
|
|
30164
|
-
return /* @__PURE__ */ jsx81("form", { ref, className: cx(LoginForm_default.form, className), ...rest, children });
|
|
30168
|
+
return /* @__PURE__ */ jsx81("form", { ref, className: cx(LoginForm_default.form, className), ...rest, children: /* @__PURE__ */ jsx81(AutofillScope, { children }) });
|
|
30165
30169
|
}
|
|
30166
30170
|
LoginFormForm.displayName = "LoginForm.Form";
|
|
30167
30171
|
function LoginFormActions({ className, children, ...rest }) {
|
|
@@ -33420,6 +33424,7 @@ function TagSelect({
|
|
|
33420
33424
|
"aria-expanded": shown,
|
|
33421
33425
|
"aria-controls": shown ? listboxId : void 0,
|
|
33422
33426
|
"aria-autocomplete": "list",
|
|
33427
|
+
...AUTOFILL_OFF,
|
|
33423
33428
|
"aria-label": ariaLabel,
|
|
33424
33429
|
"aria-labelledby": ariaLabelledBy,
|
|
33425
33430
|
"aria-invalid": ids.invalid || void 0,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
AUTOFILL_OFF,
|
|
4
5
|
Button,
|
|
5
6
|
ColorSwatches,
|
|
6
7
|
FieldActionButton,
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
useInheritedDisabled,
|
|
24
25
|
useLabels,
|
|
25
26
|
useOpenState
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-PFASPQSJ.js";
|
|
27
28
|
import {
|
|
28
29
|
cx,
|
|
29
30
|
useHostTier
|
|
@@ -176,6 +177,7 @@ function ChannelField({
|
|
|
176
177
|
/* @__PURE__ */ jsx(
|
|
177
178
|
"input",
|
|
178
179
|
{
|
|
180
|
+
...AUTOFILL_OFF,
|
|
179
181
|
ref: inputRef,
|
|
180
182
|
"aria-label": labels[channel],
|
|
181
183
|
className: cx(fieldInputTextClass, ColorPicker_default.channelInput),
|
|
@@ -200,6 +202,7 @@ function StripHexField() {
|
|
|
200
202
|
children: /* @__PURE__ */ jsx(
|
|
201
203
|
"input",
|
|
202
204
|
{
|
|
205
|
+
...AUTOFILL_OFF,
|
|
203
206
|
ref: inputRef,
|
|
204
207
|
"aria-label": labels.hex,
|
|
205
208
|
autoCapitalize: "off",
|
package/dist/color-picker.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
ColorField,
|
|
5
5
|
ColorPicker,
|
|
6
6
|
parseColor
|
|
7
|
-
} from "./chunks/chunk-
|
|
8
|
-
import "./chunks/chunk-
|
|
7
|
+
} from "./chunks/chunk-WGXXPJDR.js";
|
|
8
|
+
import "./chunks/chunk-PFASPQSJ.js";
|
|
9
9
|
import "./chunks/chunk-HMLQCKRL.js";
|
|
10
10
|
export {
|
|
11
11
|
ColorField,
|
|
@@ -151,7 +151,7 @@ export type InputFieldProps = Omit<React.InputHTMLAttributes<HTMLInputElement>,
|
|
|
151
151
|
ref?: React.Ref<HTMLInputElement>;
|
|
152
152
|
};
|
|
153
153
|
/** The `<input>`; its id comes from `Input.Root` so the label, hint and error stay linked. */
|
|
154
|
-
declare function InputField({ className, "aria-describedby": ariaDescribedBy, required, disabled: disabledProp, type, onChange, onValueChange, onFocus, onBlur, onKeyDown, onClick, onInvalid, ref, ...rest }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
154
|
+
declare function InputField({ className, "aria-describedby": ariaDescribedBy, required, disabled: disabledProp, type, autoComplete, onChange, onValueChange, onFocus, onBlur, onKeyDown, onClick, onInvalid, ref, ...rest }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
155
155
|
declare namespace InputField {
|
|
156
156
|
var displayName: string;
|
|
157
157
|
}
|
|
@@ -124,7 +124,11 @@ export type LoginFormFormProps = {
|
|
|
124
124
|
children?: React.ReactNode;
|
|
125
125
|
ref?: React.Ref<HTMLFormElement>;
|
|
126
126
|
} & React.FormHTMLAttributes<HTMLFormElement>;
|
|
127
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* The `<form>`: fields, then the submit button, one column with field → field spacing. Native
|
|
129
|
+
* submit and `onSubmit` work as usual. Autofill is the point of a sign-in form: a kit field inside
|
|
130
|
+
* it without an `autoComplete` token is left to the browser and the password managers.
|
|
131
|
+
*/
|
|
128
132
|
declare function LoginFormForm({ className, children, ref, ...rest }: LoginFormFormProps): import("react/jsx-runtime").JSX.Element;
|
|
129
133
|
declare namespace LoginFormForm {
|
|
130
134
|
var displayName: string;
|
|
@@ -100,7 +100,7 @@ export type TextareaRootProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaEl
|
|
|
100
100
|
* A multi-line field. `className` goes to the field frame, `id`, `ref` and the native attributes
|
|
101
101
|
* to the `<textarea>` (it has no `Field` part).
|
|
102
102
|
*/
|
|
103
|
-
declare function TextareaRoot({ id, className, size: sizeProp, invalid, focusRing, label, required, optional, hint, hintTone, error, counter, reserveSupportRow, autoResize, rows, maxRows, disabled: disabledProp, readOnly, value, onInput, onChange, onValueChange, onFocus, onBlur, onKeyDown, onSelect, onCommit, formatting, labels: labelsProp, "aria-describedby": ariaDescribedBy, children, ref, ...rest }: TextareaRootProps): import("react/jsx-runtime").JSX.Element;
|
|
103
|
+
declare function TextareaRoot({ id, className, size: sizeProp, invalid, focusRing, label, required, optional, hint, hintTone, error, counter, reserveSupportRow, autoResize, rows, maxRows, disabled: disabledProp, readOnly, value, autoComplete, onInput, onChange, onValueChange, onFocus, onBlur, onKeyDown, onSelect, onCommit, formatting, labels: labelsProp, "aria-describedby": ariaDescribedBy, children, ref, ...rest }: TextareaRootProps): import("react/jsx-runtime").JSX.Element;
|
|
104
104
|
declare namespace TextareaRoot {
|
|
105
105
|
var displayName: string;
|
|
106
106
|
}
|
package/dist/index.js
CHANGED
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
useSidebar,
|
|
91
91
|
useTokenValue,
|
|
92
92
|
useUploadQueue
|
|
93
|
-
} from "./chunks/chunk-
|
|
93
|
+
} from "./chunks/chunk-UNNQITFE.js";
|
|
94
94
|
import {
|
|
95
95
|
Avatar,
|
|
96
96
|
Badge,
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
tierMappings,
|
|
126
126
|
tones,
|
|
127
127
|
variants
|
|
128
|
-
} from "./chunks/chunk-
|
|
128
|
+
} from "./chunks/chunk-PFASPQSJ.js";
|
|
129
129
|
import {
|
|
130
130
|
ControlSizeProvider,
|
|
131
131
|
useOptionalControlSize
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Browser autofill and password managers stay off a kit text field: a product screen is full of
|
|
4
|
+
* searches, filters and record fields that a saved login or address has no business filling.
|
|
5
|
+
* `autocomplete="off"` turns off the browser's own suggestions where it is honoured (Chrome and
|
|
6
|
+
* Firefox history and address autofill on desktop); browsers still read login-looking fields on
|
|
7
|
+
* their own, and the password-manager extensions read only their own opt-outs: 1Password
|
|
8
|
+
* `data-1p-ignore`, LastPass `data-lpignore`, Bitwarden `data-bwignore`, Dashlane
|
|
9
|
+
* `data-form-type="other"`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const AUTOFILL_OFF: {
|
|
12
|
+
readonly autoComplete: "off";
|
|
13
|
+
readonly "data-1p-ignore": "true";
|
|
14
|
+
readonly "data-lpignore": "true";
|
|
15
|
+
readonly "data-bwignore": "true";
|
|
16
|
+
readonly "data-form-type": "other";
|
|
17
|
+
};
|
|
18
|
+
type AutofillProps = {
|
|
19
|
+
autoComplete?: string;
|
|
20
|
+
} & Partial<Omit<typeof AUTOFILL_OFF, "autoComplete">>;
|
|
21
|
+
export declare function AutofillScope({ children }: {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
/**
|
|
25
|
+
* The autofill attributes of a text field. A consumer's `autoComplete` token other than `"off"`
|
|
26
|
+
* opts in: the token goes through and the opt-outs are dropped. Without a token the field is off,
|
|
27
|
+
* except inside `AutofillScope`, where the browser and the password managers decide.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useAutofill(autoComplete: string | undefined): AutofillProps;
|
|
30
|
+
export {};
|
package/dist/skill/checklist.md
CHANGED
|
@@ -39,6 +39,8 @@ checks.
|
|
|
39
39
|
- [ ] A request a value starts — a simulated one too — is cancelled when the value changes again ([nothing to guess](ux.md#6-nothing-to-guess)).
|
|
40
40
|
- [ ] Tabs, filters, search, page and the open record are in the URL ([nothing to guess](ux.md#6-nothing-to-guess)).
|
|
41
41
|
- [ ] Defaults prefill what the system knows; each field has the control and `inputMode` of its data ([ask less](ux.md#7-ask-less)).
|
|
42
|
+
- [ ] `autoComplete` tokens only on sign-in and sign-up fields (`LoginForm.Form`, a password change); no token on a search or a record field ([ask less](ux.md#7-ask-less)).
|
|
43
|
+
- [ ] No search, filter or record field (other than a record's e-mail) says «username», «login», «email», «логин», «почта» in its placeholder, label, `name` or `id` — Safari offers saved logins there ([ask less](ux.md#7-ask-less)).
|
|
42
44
|
- [ ] I looked at the screen in a browser, filled badly ([the visual check](ux.md#9-look-at-it-before-handing-it-over)).
|
|
43
45
|
|
|
44
46
|
## Grid and spacing
|
|
@@ -180,11 +180,7 @@ export default function DrawerScreenExample() {
|
|
|
180
180
|
</Radio.Group>
|
|
181
181
|
<Input.Root label="Название" error={errors.name}>
|
|
182
182
|
<Input.Wrapper>
|
|
183
|
-
<Input.Field
|
|
184
|
-
name="name"
|
|
185
|
-
placeholder="ООО «Ромашка»"
|
|
186
|
-
autoComplete="organization"
|
|
187
|
-
/>
|
|
183
|
+
<Input.Field name="name" placeholder="ООО «Ромашка»" />
|
|
188
184
|
</Input.Wrapper>
|
|
189
185
|
</Input.Root>
|
|
190
186
|
<div className={styles.pair}>
|
|
@@ -96,6 +96,7 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
|
|
|
96
96
|
| `onValueChange` | `(value: string) => void` | — | Called with the new string; native `onChange` still fires first. |
|
|
97
97
|
| `aria-describedby` | `string` | — | Merged with the affix and hint/error ids from the root. |
|
|
98
98
|
| `required` | `boolean` | — | Overrides the root's `required` for the native input. |
|
|
99
|
+
| `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
|
|
99
100
|
| `…rest` | `Omit<InputHTMLAttributes<HTMLInputElement>, "size" \| "id">` | — | `value`, `defaultValue`, `onChange`, `type`, `disabled`, `readOnly`, `maxLength`, `placeholder`… |
|
|
100
101
|
|
|
101
102
|
### Input.Icon
|
|
@@ -272,6 +273,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
272
273
|
- `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
|
|
273
274
|
- `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
|
|
274
275
|
- `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
|
|
276
|
+
- Autofill is off unless you pass `autoComplete`: without a token the input carries `autocomplete="off"` and the opt-outs of 1Password, LastPass, Bitwarden and Dashlane (`data-1p-ignore`, `data-lpignore`, `data-bwignore`, `data-form-type="other"`); a token passes through and drops them. With `suggestions` it stays off. Inside `LoginForm.Form` a field without a token is left to the browser.
|
|
275
277
|
- A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
|
|
276
278
|
- `Input.ClearButton` is a real button named by `labels.clear`, with `aria-controls` on the input.
|
|
277
279
|
- `Input.Action` and `Input.CopyButton` are buttons (or a link with `asChild`) named by their label, with a tooltip; copying announces `labels.copied` through a polite live region.
|
|
@@ -338,6 +340,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
338
340
|
- A Select and an Input side by side for one value (an icon + a name) → the Select inside `Input.Wrapper`, named by its `aria-label`.
|
|
339
341
|
- A text field with a hand-made time mask, or a Datepicker for a time of day → `Input.Field type="time"`.
|
|
340
342
|
- Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
|
|
343
|
+
- `autoComplete="email"` / `"name"` on a search, filter or record field → leave it out: autofill is off unless you pass `autoComplete`; tokens belong on sign-in and sign-up fields (`LoginForm.Form`) and on a new password (`new-password`).
|
|
341
344
|
|
|
342
345
|
## Related
|
|
343
346
|
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
|
|
@@ -28,7 +28,7 @@ export default function InputValidationExample() {
|
|
|
28
28
|
<>
|
|
29
29
|
<Input.Root label="Название организации" required hint="Как в учредительных документах">
|
|
30
30
|
<Input.Wrapper>
|
|
31
|
-
<Input.Field defaultValue="ООО «Северный ветер»"
|
|
31
|
+
<Input.Field defaultValue="ООО «Северный ветер»" />
|
|
32
32
|
</Input.Wrapper>
|
|
33
33
|
</Input.Root>
|
|
34
34
|
<Input.Root
|
|
@@ -99,7 +99,7 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
|
|
|
99
99
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | The other div attributes. |
|
|
100
100
|
|
|
101
101
|
### LoginForm.Form
|
|
102
|
-
`ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props.
|
|
102
|
+
`ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props. The one place autofill stays on: a kit field inside it without `autoComplete` is left to the browser and the password managers.
|
|
103
103
|
|
|
104
104
|
| Prop | Type | Default | Description |
|
|
105
105
|
|---|---|---|---|
|
|
@@ -184,7 +184,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
|
|
|
184
184
|
- The title is a real heading (`h1` by default) and the form is a native `<form>`: Enter submits.
|
|
185
185
|
- The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
|
|
186
186
|
- Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
|
|
187
|
-
-
|
|
187
|
+
- Every field names its purpose with `autoComplete` — `email` or `username`, `current-password` to sign in, `new-password` to sign up, reset or accept an invite, `one-time-code` for a code (`DigitInput` sets it) — so password managers fill and save the right field. Kit fields elsewhere keep autofill off.
|
|
188
188
|
- Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the e-mail takes `invalid` and the `error` of the password says why, announced by the support row of the field.
|
|
189
189
|
|
|
190
190
|
### Labels
|
|
@@ -16,12 +16,16 @@ export default function LoginFormSizesExample() {
|
|
|
16
16
|
<LoginForm.Form onSubmit={(e) => e.preventDefault()}>
|
|
17
17
|
<Input.Root label="Email">
|
|
18
18
|
<Input.Wrapper>
|
|
19
|
-
<Input.Field type="email" placeholder="name@company.ru" />
|
|
19
|
+
<Input.Field type="email" autoComplete="email" placeholder="name@company.ru" />
|
|
20
20
|
</Input.Wrapper>
|
|
21
21
|
</Input.Root>
|
|
22
22
|
<Input.Root label="Пароль">
|
|
23
23
|
<Input.Wrapper>
|
|
24
|
-
<Input.Field
|
|
24
|
+
<Input.Field
|
|
25
|
+
type="password"
|
|
26
|
+
autoComplete="current-password"
|
|
27
|
+
placeholder="********"
|
|
28
|
+
/>
|
|
25
29
|
</Input.Wrapper>
|
|
26
30
|
</Input.Root>
|
|
27
31
|
<Button.Root type="submit" fullWidth>
|
|
@@ -38,7 +38,7 @@ export default function TabsOrientationExample() {
|
|
|
38
38
|
<div className={styles.form}>
|
|
39
39
|
<Input.Root label="Имя и фамилия">
|
|
40
40
|
<Input.Wrapper>
|
|
41
|
-
<Input.Field defaultValue="Анна Смирнова"
|
|
41
|
+
<Input.Field defaultValue="Анна Смирнова" />
|
|
42
42
|
</Input.Wrapper>
|
|
43
43
|
</Input.Root>
|
|
44
44
|
<Input.Root label="Должность">
|
|
@@ -60,6 +60,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
|
|
|
60
60
|
| `aria-describedby` | `string` | — | Merged before the hint/error ids. |
|
|
61
61
|
| `onCommit` | `(value: string) => void \| Promise<void>` | — | Inline save: fires with a changed value on blur and Mod+Enter; Escape reverts. Return a Promise: the field shows a spinner and stays editable; a rejection restores the value — show why through `error`. |
|
|
62
62
|
| `formatting` | `boolean` | `false` | Selecting text shows a floating toolbar of eight Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. Off: plain text. |
|
|
63
|
+
| `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
|
|
63
64
|
| `children` | `ReactNode` | — | Actions in the field's top end corner: `Textarea.CopyButton`, `Textarea.Action`. |
|
|
64
65
|
| `labels` | `Partial<TextareaLabels>` | — | Built-in strings, see Labels. |
|
|
65
66
|
| `className` | `string` | — | Class on the field frame `<div>` (label, field box, support row), as on every field root. |
|
|
@@ -226,6 +227,7 @@ Inline save (`onCommit`): the text at focus is the committed one; blur and Mod+E
|
|
|
226
227
|
- A copy `Button` under a read-only result → `Textarea.CopyButton` as a child of the root.
|
|
227
228
|
- Other children than `Textarea.CopyButton` / `Textarea.Action` → the root renders the textarea itself; children are only the corner actions.
|
|
228
229
|
- `formatting` on text that is shown as plain text → the reader sees `**` and `||`; turn it on only where the text is rendered with its Markdown.
|
|
230
|
+
- Expecting the browser to offer saved addresses in a note → autofill is off unless you pass `autoComplete` (a token such as `street-address` opts in).
|
|
229
231
|
|
|
230
232
|
## Related
|
|
231
233
|
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (corner actions, formatting tools), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [Button](../button/COMPONENT.md) and [Kbd](../kbd/COMPONENT.md) (formatting tools), Icon (`format.*`); `internal/textFormatting` + `internal/textFormattingToolbar` (the formatting shared with Composer), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
package/dist/skill/ux.md
CHANGED
|
@@ -256,8 +256,25 @@ Which button starts each step and how the confirm behaves:
|
|
|
256
256
|
person's own company, the last used currency. An empty required field the person always fills the
|
|
257
257
|
same way is a missing default.
|
|
258
258
|
- **The right control for the data:** `inputMode="numeric" | "decimal" | "tel" | "email"`,
|
|
259
|
-
`
|
|
260
|
-
|
|
259
|
+
`Datepicker` for a date, `Select` for a closed list. A free text field for a closed set of values
|
|
260
|
+
is a bug.
|
|
261
|
+
- **Autofill only where a person signs in:** `autoComplete` tokens go on sign-in and sign-up fields
|
|
262
|
+
(`email`, `current-password`, `new-password`, `one-time-code`) and on a new password; everywhere
|
|
263
|
+
else the kit keeps autofill off, so a saved login never drops over a search or a record field.
|
|
264
|
+
- **Safari decides by words, not by attributes:** it ignores `autocomplete="off"` on a field it
|
|
265
|
+
takes for a login, and it takes a text field for one when its placeholder, label, `name` or `id`
|
|
266
|
+
speaks of a login: «username», «user», «login», «email», «e-mail», «логин», «почта», «имя
|
|
267
|
+
пользователя». The kit's opt-outs stop Chrome, Firefox and the password-manager extensions, not
|
|
268
|
+
Safari; on a field that is not a sign-in only the words do. So a search, a filter or a record
|
|
269
|
+
field never names itself with those words — it says what is searched in the domain's words:
|
|
270
|
+
- bad: a search `placeholder="Название или @username"`, `name="login"` on a filter, «Поиск по
|
|
271
|
+
email» → Safari offers the saved login over the table;
|
|
272
|
+
- good: `placeholder="Название или @ник"`, «Имя или контакт», «Клиент, телефон или ИНН»; no
|
|
273
|
+
`name` / `id` on a search at all.
|
|
274
|
+
A record's own e-mail field (a client's, an employee's) keeps its label «Почта» and
|
|
275
|
+
`type="email"`: Safari may offer a contact card there, which is right for e-mail. Never fight the
|
|
276
|
+
browser with tricks — a readonly-until-focus field, a hidden decoy, a made-up `autoComplete`
|
|
277
|
+
token: they break the field for assistive tech and stop working with the next Safari.
|
|
261
278
|
- **Progressive disclosure:** the common 80% first; rare settings behind an `Accordion` item or a
|
|
262
279
|
«Дополнительно» section; a long create flow in steps (`Stepper`).
|
|
263
280
|
- **Group by meaning, order by use:** fields in the order a person has the information (company →
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prime-ui-kit",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.4",
|
|
4
4
|
"packageManager": "bun@1.3.13",
|
|
5
5
|
"description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
|
|
6
6
|
"bin": {
|
|
@@ -180,11 +180,7 @@ export default function DrawerScreenExample() {
|
|
|
180
180
|
</Radio.Group>
|
|
181
181
|
<Input.Root label="Название" error={errors.name}>
|
|
182
182
|
<Input.Wrapper>
|
|
183
|
-
<Input.Field
|
|
184
|
-
name="name"
|
|
185
|
-
placeholder="ООО «Ромашка»"
|
|
186
|
-
autoComplete="organization"
|
|
187
|
-
/>
|
|
183
|
+
<Input.Field name="name" placeholder="ООО «Ромашка»" />
|
|
188
184
|
</Input.Wrapper>
|
|
189
185
|
</Input.Root>
|
|
190
186
|
<div className={styles.pair}>
|
|
@@ -96,6 +96,7 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
|
|
|
96
96
|
| `onValueChange` | `(value: string) => void` | — | Called with the new string; native `onChange` still fires first. |
|
|
97
97
|
| `aria-describedby` | `string` | — | Merged with the affix and hint/error ids from the root. |
|
|
98
98
|
| `required` | `boolean` | — | Overrides the root's `required` for the native input. |
|
|
99
|
+
| `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
|
|
99
100
|
| `…rest` | `Omit<InputHTMLAttributes<HTMLInputElement>, "size" \| "id">` | — | `value`, `defaultValue`, `onChange`, `type`, `disabled`, `readOnly`, `maxLength`, `placeholder`… |
|
|
100
101
|
|
|
101
102
|
### Input.Icon
|
|
@@ -272,6 +273,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
272
273
|
- `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
|
|
273
274
|
- `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
|
|
274
275
|
- `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
|
|
276
|
+
- Autofill is off unless you pass `autoComplete`: without a token the input carries `autocomplete="off"` and the opt-outs of 1Password, LastPass, Bitwarden and Dashlane (`data-1p-ignore`, `data-lpignore`, `data-bwignore`, `data-form-type="other"`); a token passes through and drops them. With `suggestions` it stays off. Inside `LoginForm.Form` a field without a token is left to the browser.
|
|
275
277
|
- A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
|
|
276
278
|
- `Input.ClearButton` is a real button named by `labels.clear`, with `aria-controls` on the input.
|
|
277
279
|
- `Input.Action` and `Input.CopyButton` are buttons (or a link with `asChild`) named by their label, with a tooltip; copying announces `labels.copied` through a polite live region.
|
|
@@ -338,6 +340,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
338
340
|
- A Select and an Input side by side for one value (an icon + a name) → the Select inside `Input.Wrapper`, named by its `aria-label`.
|
|
339
341
|
- A text field with a hand-made time mask, or a Datepicker for a time of day → `Input.Field type="time"`.
|
|
340
342
|
- Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
|
|
343
|
+
- `autoComplete="email"` / `"name"` on a search, filter or record field → leave it out: autofill is off unless you pass `autoComplete`; tokens belong on sign-in and sign-up fields (`LoginForm.Form`) and on a new password (`new-password`).
|
|
341
344
|
|
|
342
345
|
## Related
|
|
343
346
|
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
|
|
@@ -28,7 +28,7 @@ export default function InputValidationExample() {
|
|
|
28
28
|
<>
|
|
29
29
|
<Input.Root label="Название организации" required hint="Как в учредительных документах">
|
|
30
30
|
<Input.Wrapper>
|
|
31
|
-
<Input.Field defaultValue="ООО «Северный ветер»"
|
|
31
|
+
<Input.Field defaultValue="ООО «Северный ветер»" />
|
|
32
32
|
</Input.Wrapper>
|
|
33
33
|
</Input.Root>
|
|
34
34
|
<Input.Root
|
|
@@ -99,7 +99,7 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
|
|
|
99
99
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | The other div attributes. |
|
|
100
100
|
|
|
101
101
|
### LoginForm.Form
|
|
102
|
-
`ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props.
|
|
102
|
+
`ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props. The one place autofill stays on: a kit field inside it without `autoComplete` is left to the browser and the password managers.
|
|
103
103
|
|
|
104
104
|
| Prop | Type | Default | Description |
|
|
105
105
|
|---|---|---|---|
|
|
@@ -184,7 +184,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
|
|
|
184
184
|
- The title is a real heading (`h1` by default) and the form is a native `<form>`: Enter submits.
|
|
185
185
|
- The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
|
|
186
186
|
- Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
|
|
187
|
-
-
|
|
187
|
+
- Every field names its purpose with `autoComplete` — `email` or `username`, `current-password` to sign in, `new-password` to sign up, reset or accept an invite, `one-time-code` for a code (`DigitInput` sets it) — so password managers fill and save the right field. Kit fields elsewhere keep autofill off.
|
|
188
188
|
- Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the e-mail takes `invalid` and the `error` of the password says why, announced by the support row of the field.
|
|
189
189
|
|
|
190
190
|
### Labels
|
|
@@ -16,12 +16,16 @@ export default function LoginFormSizesExample() {
|
|
|
16
16
|
<LoginForm.Form onSubmit={(e) => e.preventDefault()}>
|
|
17
17
|
<Input.Root label="Email">
|
|
18
18
|
<Input.Wrapper>
|
|
19
|
-
<Input.Field type="email" placeholder="name@company.ru" />
|
|
19
|
+
<Input.Field type="email" autoComplete="email" placeholder="name@company.ru" />
|
|
20
20
|
</Input.Wrapper>
|
|
21
21
|
</Input.Root>
|
|
22
22
|
<Input.Root label="Пароль">
|
|
23
23
|
<Input.Wrapper>
|
|
24
|
-
<Input.Field
|
|
24
|
+
<Input.Field
|
|
25
|
+
type="password"
|
|
26
|
+
autoComplete="current-password"
|
|
27
|
+
placeholder="********"
|
|
28
|
+
/>
|
|
25
29
|
</Input.Wrapper>
|
|
26
30
|
</Input.Root>
|
|
27
31
|
<Button.Root type="submit" fullWidth>
|
|
@@ -38,7 +38,7 @@ export default function TabsOrientationExample() {
|
|
|
38
38
|
<div className={styles.form}>
|
|
39
39
|
<Input.Root label="Имя и фамилия">
|
|
40
40
|
<Input.Wrapper>
|
|
41
|
-
<Input.Field defaultValue="Анна Смирнова"
|
|
41
|
+
<Input.Field defaultValue="Анна Смирнова" />
|
|
42
42
|
</Input.Wrapper>
|
|
43
43
|
</Input.Root>
|
|
44
44
|
<Input.Root label="Должность">
|
|
@@ -60,6 +60,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
|
|
|
60
60
|
| `aria-describedby` | `string` | — | Merged before the hint/error ids. |
|
|
61
61
|
| `onCommit` | `(value: string) => void \| Promise<void>` | — | Inline save: fires with a changed value on blur and Mod+Enter; Escape reverts. Return a Promise: the field shows a spinner and stays editable; a rejection restores the value — show why through `error`. |
|
|
62
62
|
| `formatting` | `boolean` | `false` | Selecting text shows a floating toolbar of eight Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. Off: plain text. |
|
|
63
|
+
| `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
|
|
63
64
|
| `children` | `ReactNode` | — | Actions in the field's top end corner: `Textarea.CopyButton`, `Textarea.Action`. |
|
|
64
65
|
| `labels` | `Partial<TextareaLabels>` | — | Built-in strings, see Labels. |
|
|
65
66
|
| `className` | `string` | — | Class on the field frame `<div>` (label, field box, support row), as on every field root. |
|
|
@@ -226,6 +227,7 @@ Inline save (`onCommit`): the text at focus is the committed one; blur and Mod+E
|
|
|
226
227
|
- A copy `Button` under a read-only result → `Textarea.CopyButton` as a child of the root.
|
|
227
228
|
- Other children than `Textarea.CopyButton` / `Textarea.Action` → the root renders the textarea itself; children are only the corner actions.
|
|
228
229
|
- `formatting` on text that is shown as plain text → the reader sees `**` and `||`; turn it on only where the text is rendered with its Markdown.
|
|
230
|
+
- Expecting the browser to offer saved addresses in a note → autofill is off unless you pass `autoComplete` (a token such as `street-address` opts in).
|
|
229
231
|
|
|
230
232
|
## Related
|
|
231
233
|
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (corner actions, formatting tools), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [Button](../button/COMPONENT.md) and [Kbd](../kbd/COMPONENT.md) (formatting tools), Icon (`format.*`); `internal/textFormatting` + `internal/textFormattingToolbar` (the formatting shared with Composer), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|