@seekalfred/primitives 0.5.0 → 0.6.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/README.md +664 -9
- package/dist/accordion/accordion.d.ts +5 -5
- package/dist/accordion/accordion.js +22 -11
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/action-button/action-button.d.ts +50 -0
- package/dist/action-button/action-button.js +159 -0
- package/dist/action-button/action-button.js.map +1 -0
- package/dist/action-button/index.d.ts +2 -0
- package/dist/action-button/index.js +2 -0
- package/dist/alert/alert.d.ts +16 -2
- package/dist/alert/alert.js +118 -20
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/alert-dialog/alert-dialog.js +4 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -1
- package/dist/avatar/avatar.js +51 -18
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +10 -3
- package/dist/badge/badge.js +37 -5
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +3 -30
- package/dist/breadcrumb/breadcrumb.js +25 -8
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +36 -23
- package/dist/button/button.js +178 -33
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.d.ts +76 -0
- package/dist/button-group/button-group.js +403 -0
- package/dist/button-group/button-group.js.map +1 -0
- package/dist/button-group/index.d.ts +2 -0
- package/dist/button-group/index.js +2 -0
- package/dist/calendar/calendar.d.ts +3 -3
- package/dist/calendar/calendar.js +263 -34
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +14 -7
- package/dist/card/card.js +32 -14
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +2 -9
- package/dist/checkbox/checkbox.js +28 -29
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/confirm-morph/confirm-morph.d.ts +85 -0
- package/dist/confirm-morph/confirm-morph.js +652 -0
- package/dist/confirm-morph/confirm-morph.js.map +1 -0
- package/dist/confirm-morph/index.d.ts +2 -0
- package/dist/confirm-morph/index.js +2 -0
- package/dist/copy-button/copy-button.d.ts +32 -0
- package/dist/copy-button/copy-button.js +134 -0
- package/dist/copy-button/copy-button.js.map +1 -0
- package/dist/copy-button/index.d.ts +2 -0
- package/dist/copy-button/index.js +2 -0
- package/dist/date-picker/date-picker.d.ts +51 -0
- package/dist/date-picker/date-picker.js +241 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/date-picker/date-range-picker.d.ts +78 -0
- package/dist/date-picker/date-range-picker.js +1015 -0
- package/dist/date-picker/date-range-picker.js.map +1 -0
- package/dist/date-picker/index.d.ts +4 -0
- package/dist/date-picker/index.js +4 -0
- package/dist/date-picker/picker-parts.d.ts +46 -0
- package/dist/date-picker/picker-parts.js +135 -0
- package/dist/date-picker/picker-parts.js.map +1 -0
- package/dist/dialog/dialog.d.ts +12 -16
- package/dist/dialog/dialog.js +45 -14
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/empty-state/empty-state.d.ts +44 -0
- package/dist/empty-state/empty-state.js +83 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/empty-state/index.d.ts +2 -0
- package/dist/empty-state/index.js +2 -0
- package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
- package/dist/expanding-button-group/expanding-button-group.js +350 -0
- package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
- package/dist/expanding-button-group/index.d.ts +2 -0
- package/dist/expanding-button-group/index.js +2 -0
- package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
- package/dist/file-drop-zone/file-drop-zone.js +116 -0
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
- package/dist/file-drop-zone/index.d.ts +2 -0
- package/dist/file-drop-zone/index.js +2 -0
- package/dist/filter-bar/filter-bar.d.ts +121 -0
- package/dist/filter-bar/filter-bar.js +300 -0
- package/dist/filter-bar/filter-bar.js.map +1 -0
- package/dist/filter-bar/index.d.ts +2 -0
- package/dist/filter-bar/index.js +2 -0
- package/dist/floating-button-group/floating-button-group.d.ts +61 -0
- package/dist/floating-button-group/floating-button-group.js +340 -0
- package/dist/floating-button-group/floating-button-group.js.map +1 -0
- package/dist/floating-button-group/index.d.ts +2 -0
- package/dist/floating-button-group/index.js +2 -0
- package/dist/icon-morph/icon-morph.d.ts +55 -0
- package/dist/icon-morph/icon-morph.js +331 -0
- package/dist/icon-morph/icon-morph.js.map +1 -0
- package/dist/icon-morph/index.d.ts +2 -0
- package/dist/icon-morph/index.js +2 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +29 -7
- package/dist/input/input.d.ts +23 -1
- package/dist/input/input.js +37 -13
- package/dist/input/input.js.map +1 -1
- package/dist/label/label.d.ts +1 -1
- package/dist/label/label.js +4 -2
- package/dist/label/label.js.map +1 -1
- package/dist/lib/count-to.js +113 -0
- package/dist/lib/count-to.js.map +1 -0
- package/dist/lib/eye-morph.js +62 -0
- package/dist/lib/eye-morph.js.map +1 -0
- package/dist/lib/field-message.js +336 -0
- package/dist/lib/field-message.js.map +1 -0
- package/dist/lib/field-shell.js +81 -0
- package/dist/lib/field-shell.js.map +1 -0
- package/dist/lib/field-surface.js +4 -2
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flip.js +80 -0
- package/dist/lib/flip.js.map +1 -0
- package/dist/lib/height-follow.js +59 -0
- package/dist/lib/height-follow.js.map +1 -0
- package/dist/lib/icon-swap.js +47 -0
- package/dist/lib/icon-swap.js.map +1 -0
- package/dist/lib/leaving-list.js +57 -0
- package/dist/lib/leaving-list.js.map +1 -0
- package/dist/lib/merge-refs.js +22 -0
- package/dist/lib/merge-refs.js.map +1 -0
- package/dist/lib/morph-text.js +128 -0
- package/dist/lib/morph-text.js.map +1 -0
- package/dist/lib/motion.js +162 -0
- package/dist/lib/motion.js.map +1 -0
- package/dist/lib/pausable-timeout.js +67 -0
- package/dist/lib/pausable-timeout.js.map +1 -0
- package/dist/lib/rolling-value.js +31 -0
- package/dist/lib/rolling-value.js.map +1 -0
- package/dist/lib/spring-progress.js +114 -0
- package/dist/lib/spring-progress.js.map +1 -0
- package/dist/lib/svg-id.js +10 -0
- package/dist/lib/svg-id.js.map +1 -0
- package/dist/lib/swap-text.js +77 -0
- package/dist/lib/swap-text.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +36 -0
- package/dist/lib/use-copy-feedback.js.map +1 -0
- package/dist/menu/menu.d.ts +5 -5
- package/dist/menu/menu.js +10 -6
- package/dist/menu/menu.js.map +1 -1
- package/dist/page-header/index.d.ts +2 -0
- package/dist/page-header/index.js +2 -0
- package/dist/page-header/page-header.d.ts +75 -0
- package/dist/page-header/page-header.js +83 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/password-field/index.d.ts +2 -0
- package/dist/password-field/index.js +2 -0
- package/dist/password-field/password-field.d.ts +37 -0
- package/dist/password-field/password-field.js +101 -0
- package/dist/password-field/password-field.js.map +1 -0
- package/dist/password-strength/index.d.ts +2 -0
- package/dist/password-strength/index.js +2 -0
- package/dist/password-strength/password-strength.d.ts +127 -0
- package/dist/password-strength/password-strength.js +455 -0
- package/dist/password-strength/password-strength.js.map +1 -0
- package/dist/popover/popover.d.ts +10 -2
- package/dist/popover/popover.js +6 -4
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +2 -2
- package/dist/progress/progress.js +50 -7
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.d.ts +3 -3
- package/dist/radio-group/radio-group.js +14 -10
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +17 -6
- package/dist/scroll-area/scroll-area.js +30 -6
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/index.d.ts +2 -0
- package/dist/search-field/index.js +2 -0
- package/dist/search-field/search-field.d.ts +26 -0
- package/dist/search-field/search-field.js +155 -0
- package/dist/search-field/search-field.js.map +1 -0
- package/dist/select/select.d.ts +3 -3
- package/dist/select/select.js +85 -24
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/separator/separator.js +4 -2
- package/dist/separator/separator.js.map +1 -1
- package/dist/sheet/sheet.d.ts +1 -1
- package/dist/sheet/sheet.js +6 -3
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/side-nav/index.d.ts +2 -0
- package/dist/side-nav/index.js +2 -0
- package/dist/side-nav/side-nav.d.ts +72 -0
- package/dist/side-nav/side-nav.js +142 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +8 -2
- package/dist/skeleton/skeleton.js +9 -3
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.d.ts +2 -2
- package/dist/slider/slider.js +64 -8
- package/dist/slider/slider.js.map +1 -1
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +4 -2
- package/dist/spinner/spinner.js.map +1 -1
- package/dist/split-button/index.d.ts +2 -0
- package/dist/split-button/index.js +2 -0
- package/dist/split-button/split-button.d.ts +46 -0
- package/dist/split-button/split-button.js +122 -0
- package/dist/split-button/split-button.js.map +1 -0
- package/dist/stat-card/index.d.ts +2 -0
- package/dist/stat-card/index.js +2 -0
- package/dist/stat-card/stat-card.d.ts +81 -0
- package/dist/stat-card/stat-card.js +152 -0
- package/dist/stat-card/stat-card.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/switch/switch.d.ts +2 -2
- package/dist/switch/switch.js +11 -8
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +38 -7
- package/dist/table/table.js +114 -12
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.d.ts +4 -4
- package/dist/tabs/tabs.js +153 -12
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +24 -1
- package/dist/textarea/textarea.js +27 -3
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/theme-switch/index.d.ts +2 -0
- package/dist/theme-switch/index.js +2 -0
- package/dist/theme-switch/theme-switch.d.ts +37 -0
- package/dist/theme-switch/theme-switch.js +123 -0
- package/dist/theme-switch/theme-switch.js.map +1 -0
- package/dist/time-picker/index.d.ts +2 -0
- package/dist/time-picker/index.js +2 -0
- package/dist/time-picker/time-picker.d.ts +54 -0
- package/dist/time-picker/time-picker.js +201 -0
- package/dist/time-picker/time-picker.js.map +1 -0
- package/dist/toast/toast.d.ts +2 -2
- package/dist/toast/toast.js +61 -39
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +3 -3
- package/dist/tooltip/tooltip.js +66 -15
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +83 -3
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from "../lib/motion.js";
|
|
4
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
5
|
+
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
6
|
+
import { Label as Label$1 } from "../label/label.js";
|
|
7
|
+
import { FieldMessage } from "../lib/field-message.js";
|
|
8
|
+
import { useFieldWiring } from "../lib/field-shell.js";
|
|
9
|
+
import { EyeMorph } from "../lib/eye-morph.js";
|
|
10
|
+
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
11
|
+
import { Button, Group, Input } from "react-aria-components";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region src/password-strength/password-strength.tsx
|
|
14
|
+
const defaultPasswordStrengthMessages = {
|
|
15
|
+
levels: {
|
|
16
|
+
tooShort: "Too short",
|
|
17
|
+
weak: "Weak",
|
|
18
|
+
fair: "Fair",
|
|
19
|
+
good: "Good",
|
|
20
|
+
strong: "Strong"
|
|
21
|
+
},
|
|
22
|
+
showPassword: "Show password",
|
|
23
|
+
meterLabel: "Password strength",
|
|
24
|
+
noPassword: "No password yet",
|
|
25
|
+
rulesLabel: "Password requirements",
|
|
26
|
+
ruleMet: ", met",
|
|
27
|
+
ruleNotMet: ", not met",
|
|
28
|
+
remainingPrefix: "",
|
|
29
|
+
remainingSuffix: "more",
|
|
30
|
+
summary: ({ label, met, total }) => `Strength: ${label}. ${met} of ${total} requirements met.`
|
|
31
|
+
};
|
|
32
|
+
/** Counts code points, so an emoji or other astral character counts as one. */
|
|
33
|
+
const countCodePoints = (password) => Array.from(password).length;
|
|
34
|
+
const defaultRuleLabels = {
|
|
35
|
+
length: (minLength) => `At least ${minLength} characters`,
|
|
36
|
+
case: "Upper and lowercase letters",
|
|
37
|
+
number: "At least one number",
|
|
38
|
+
symbol: "At least one symbol"
|
|
39
|
+
};
|
|
40
|
+
/** The built-in rules (length, mixed case, number, symbol) with your own minimum length, labels and character count. */
|
|
41
|
+
function createPasswordRules({ minLength = 12, labels, countCharacters = countCodePoints } = {}) {
|
|
42
|
+
const copy = {
|
|
43
|
+
...defaultRuleLabels,
|
|
44
|
+
...labels
|
|
45
|
+
};
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
id: "length",
|
|
49
|
+
label: copy.length(minLength),
|
|
50
|
+
test: (password) => countCharacters(password) >= minLength,
|
|
51
|
+
remaining: (password) => Math.max(0, minLength - countCharacters(password))
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "case",
|
|
55
|
+
label: copy.case,
|
|
56
|
+
test: (password) => /\p{Ll}/u.test(password) && /\p{Lu}/u.test(password)
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "number",
|
|
60
|
+
label: copy.number,
|
|
61
|
+
test: (password) => /\p{N}/u.test(password)
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "symbol",
|
|
65
|
+
label: copy.symbol,
|
|
66
|
+
test: (password) => /[^\p{L}\p{N}\s]/u.test(password)
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
const defaultPasswordRules = createPasswordRules();
|
|
71
|
+
/** Scores a password on the device: one step per share of rules met, capped at the first step while it is shorter than the short length (8 by default). */
|
|
72
|
+
function estimateStrength(password, rules = defaultPasswordRules, { shortLength = 8, levels, countCharacters = countCodePoints } = {}) {
|
|
73
|
+
if (!password) return {
|
|
74
|
+
level: 0,
|
|
75
|
+
label: "",
|
|
76
|
+
met: []
|
|
77
|
+
};
|
|
78
|
+
const words = {
|
|
79
|
+
...defaultPasswordStrengthMessages.levels,
|
|
80
|
+
...levels
|
|
81
|
+
};
|
|
82
|
+
const met = rules.filter((rule) => rule.test(password)).map((rule) => rule.id);
|
|
83
|
+
if (countCharacters(password) < shortLength) return {
|
|
84
|
+
level: 1,
|
|
85
|
+
label: words.tooShort,
|
|
86
|
+
met,
|
|
87
|
+
short: true
|
|
88
|
+
};
|
|
89
|
+
const level = Math.min(4, Math.max(1, Math.round(met.length / Math.max(rules.length, 1) * 4)));
|
|
90
|
+
return {
|
|
91
|
+
level,
|
|
92
|
+
label: [
|
|
93
|
+
words.weak,
|
|
94
|
+
words.fair,
|
|
95
|
+
words.good,
|
|
96
|
+
words.strong
|
|
97
|
+
][level - 1] ?? words.weak,
|
|
98
|
+
met
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The field's box: a React Aria `Group` with the shared field surface (see
|
|
103
|
+
* PasswordField for why a Group and for `data-focus-within`). Hover darkens the
|
|
104
|
+
* border only to `muted-foreground`, as the reference does here, and a new error
|
|
105
|
+
* shakes the box (two animation names, so each restart plays).
|
|
106
|
+
*/
|
|
107
|
+
const shellClass = cn(fieldSurfaceVariants(), "flex items-center gap-1 h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [--field-toggle-size:calc(var(--spacing)*8.5)] [&:where([data-focus-within])]:border-foreground data-[hovered]:not-data-[disabled]:not-data-[focus-within]:not-data-[invalid]:border-muted-foreground motion-safe:data-[shake=a]:animate-field-shake-a motion-safe:data-[shake=b]:animate-field-shake-b");
|
|
108
|
+
const inputClass = "min-w-0 flex-1 border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed motion-safe:data-[reveal=shown]:animate-field-reveal-shown motion-safe:data-[reveal=hidden]:animate-field-reveal-hidden";
|
|
109
|
+
const toggleClass = "field-toggle-button grid size-(--field-toggle-size) flex-none cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 text-muted-foreground [--press-scale:.94] [-webkit-tap-highlight-color:transparent] outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground aria-pressed:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed";
|
|
110
|
+
const toPlaces = (value, seq, previous) => {
|
|
111
|
+
const digits = String(value).split("");
|
|
112
|
+
return digits.map((digit, index) => {
|
|
113
|
+
const key = digits.length - 1 - index;
|
|
114
|
+
return {
|
|
115
|
+
key,
|
|
116
|
+
digit,
|
|
117
|
+
seq: previous.get(key)?.seq ?? seq
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
function LeavingDigit({ id, rect, onDone, children }) {
|
|
122
|
+
const remove = useCallback(() => onDone(id), [onDone, id]);
|
|
123
|
+
useExitBackstop(true, remove);
|
|
124
|
+
return /* @__PURE__ */ jsx("span", {
|
|
125
|
+
"aria-hidden": "true",
|
|
126
|
+
onAnimationEnd: (event) => {
|
|
127
|
+
if (event.target === event.currentTarget) remove();
|
|
128
|
+
},
|
|
129
|
+
style: rect,
|
|
130
|
+
className: "absolute motion-safe:animate-field-count-out motion-reduce:animate-fade-out",
|
|
131
|
+
children
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function PlaceColumn({ place, direction, leaving, onDone }) {
|
|
135
|
+
const digitRef = useRef(null);
|
|
136
|
+
const digits = useLeavingItems(place.digit, place.digit, () => digitRef.current);
|
|
137
|
+
const done = useCallback(() => onDone(place.key), [onDone, place.key]);
|
|
138
|
+
useExitBackstop(leaving, done, 900);
|
|
139
|
+
useIsomorphicLayoutEffect(() => {
|
|
140
|
+
if (leaving && prefersReducedMotion()) done();
|
|
141
|
+
}, [leaving, done]);
|
|
142
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
143
|
+
style: { "--roll": direction },
|
|
144
|
+
onAnimationEnd: (event) => {
|
|
145
|
+
if (leaving && event.target === event.currentTarget && event.animationName === "field-column-shrink") done();
|
|
146
|
+
},
|
|
147
|
+
className: cn("field-number-column overflow-x-clip", leaving ? "motion-safe:animate-field-column-out" : place.seq > 0 && "motion-safe:animate-field-column-in"),
|
|
148
|
+
children: [digits.leaving.map((item) => /* @__PURE__ */ jsx(LeavingDigit, {
|
|
149
|
+
id: item.id,
|
|
150
|
+
rect: item.rect,
|
|
151
|
+
onDone: digits.remove,
|
|
152
|
+
children: item.value
|
|
153
|
+
}, item.id)), /* @__PURE__ */ jsx("span", {
|
|
154
|
+
ref: digitRef,
|
|
155
|
+
className: digits.changed ? "motion-safe:animate-field-count-in motion-reduce:animate-field-swap-fade-in" : void 0,
|
|
156
|
+
children: place.digit
|
|
157
|
+
}, place.digit)]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** The count beside an unmet rule. It rolls digit by digit: arriving from below when the count grew, from above when it fell, and a gained or lost digit opens or closes its column. */
|
|
161
|
+
function RollingCount({ value }) {
|
|
162
|
+
const [state, setState] = useState(() => ({
|
|
163
|
+
value,
|
|
164
|
+
direction: 1,
|
|
165
|
+
seq: 0,
|
|
166
|
+
places: toPlaces(value, 0, /* @__PURE__ */ new Map()),
|
|
167
|
+
gone: []
|
|
168
|
+
}));
|
|
169
|
+
let { places, gone, direction } = state;
|
|
170
|
+
if (state.value !== value) {
|
|
171
|
+
const seq = state.seq + 1;
|
|
172
|
+
direction = value > state.value ? 1 : -1;
|
|
173
|
+
places = toPlaces(value, seq, new Map(state.places.map((place) => [place.key, place])));
|
|
174
|
+
const kept = new Set(places.map((place) => place.key));
|
|
175
|
+
gone = [...state.gone.filter((place) => !kept.has(place.key)), ...state.places.filter((place) => !kept.has(place.key))];
|
|
176
|
+
setState({
|
|
177
|
+
value,
|
|
178
|
+
direction,
|
|
179
|
+
seq,
|
|
180
|
+
places,
|
|
181
|
+
gone
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const finish = useCallback((key) => setState((current) => ({
|
|
185
|
+
...current,
|
|
186
|
+
gone: current.gone.filter((place) => place.key !== key)
|
|
187
|
+
})), []);
|
|
188
|
+
const all = [...places.map((place) => ({
|
|
189
|
+
place,
|
|
190
|
+
leaving: false
|
|
191
|
+
})), ...gone.map((place) => ({
|
|
192
|
+
place,
|
|
193
|
+
leaving: true
|
|
194
|
+
}))].sort((a, b) => b.place.key - a.place.key);
|
|
195
|
+
return /* @__PURE__ */ jsx("span", {
|
|
196
|
+
className: "inline-flex tabular-nums",
|
|
197
|
+
children: all.map(({ place, leaving }) => /* @__PURE__ */ jsx(PlaceColumn, {
|
|
198
|
+
place,
|
|
199
|
+
direction,
|
|
200
|
+
leaving,
|
|
201
|
+
onDone: finish
|
|
202
|
+
}, place.key))
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** One rule's tick: the tint grows under it while it draws from its short stroke; unchecking retracts it faster than it drew. */
|
|
206
|
+
function RuleMark() {
|
|
207
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
208
|
+
"aria-hidden": "true",
|
|
209
|
+
className: "field-rule-mark",
|
|
210
|
+
children: [/* @__PURE__ */ jsx("span", { className: "field-rule-fill" }), /* @__PURE__ */ jsx("svg", {
|
|
211
|
+
className: "field-rule-glyph",
|
|
212
|
+
viewBox: "0 0 16 16",
|
|
213
|
+
fill: "none",
|
|
214
|
+
stroke: "currentColor",
|
|
215
|
+
strokeWidth: 1.75,
|
|
216
|
+
strokeLinecap: "round",
|
|
217
|
+
strokeLinejoin: "round",
|
|
218
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
219
|
+
d: "M4.75 8.25 7 10.5l4.25-4.75",
|
|
220
|
+
pathLength: 1,
|
|
221
|
+
className: "field-rule-tick"
|
|
222
|
+
})
|
|
223
|
+
})]
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A new-password input with a strength meter and a checklist (see
|
|
228
|
+
* {@link PasswordStrengthProps}).
|
|
229
|
+
*/
|
|
230
|
+
const PasswordStrength = forwardRef(function PasswordStrength({ label, value: valueProp, defaultValue = "", onValueChange, onChange, rules = defaultPasswordRules, error, revealed: revealedProp, onRevealedChange, messages: messagesProp, shortLength, countCharacters, className, ...props }, ref) {
|
|
231
|
+
const messages = {
|
|
232
|
+
...defaultPasswordStrengthMessages,
|
|
233
|
+
...messagesProp,
|
|
234
|
+
levels: {
|
|
235
|
+
...defaultPasswordStrengthMessages.levels,
|
|
236
|
+
...messagesProp?.levels
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const options = {
|
|
240
|
+
...shortLength === void 0 ? {} : { shortLength },
|
|
241
|
+
levels: messages.levels,
|
|
242
|
+
...countCharacters === void 0 ? {} : { countCharacters }
|
|
243
|
+
};
|
|
244
|
+
const field = useFieldWiring({
|
|
245
|
+
enabled: true,
|
|
246
|
+
id: props.id,
|
|
247
|
+
describedBy: props["aria-describedby"],
|
|
248
|
+
invalid: props["aria-invalid"],
|
|
249
|
+
description: void 0,
|
|
250
|
+
error
|
|
251
|
+
});
|
|
252
|
+
const rulesId = `${field.controlId}-rules`;
|
|
253
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
254
|
+
const value = valueProp ?? internal;
|
|
255
|
+
const [revealedInternal, setRevealedInternal] = useState(false);
|
|
256
|
+
const revealed = revealedProp ?? revealedInternal;
|
|
257
|
+
const strength = estimateStrength(value, rules, options);
|
|
258
|
+
const met = new Set(strength.met);
|
|
259
|
+
const rank = strength.short ? .5 : strength.level;
|
|
260
|
+
const metKey = strength.met.join(" ");
|
|
261
|
+
const [track, setTrack] = useState({
|
|
262
|
+
rank,
|
|
263
|
+
level: strength.level,
|
|
264
|
+
previousLevel: strength.level,
|
|
265
|
+
direction: 1,
|
|
266
|
+
metKey,
|
|
267
|
+
previousMet: metKey
|
|
268
|
+
});
|
|
269
|
+
if (track.rank !== rank || track.metKey !== metKey) setTrack({
|
|
270
|
+
rank,
|
|
271
|
+
level: strength.level,
|
|
272
|
+
previousLevel: track.rank !== rank ? track.level : track.previousLevel,
|
|
273
|
+
direction: track.rank === rank ? track.direction : rank > track.rank ? 1 : -1,
|
|
274
|
+
metKey,
|
|
275
|
+
previousMet: track.metKey !== metKey ? track.metKey : track.previousMet
|
|
276
|
+
});
|
|
277
|
+
const previousMet = new Set(track.previousMet.split(" ").filter(Boolean));
|
|
278
|
+
const changed = rules.filter((rule) => met.has(rule.id) !== previousMet.has(rule.id)).map((rule) => rule.id);
|
|
279
|
+
const [reveal, setReveal] = useState({
|
|
280
|
+
revealed,
|
|
281
|
+
changed: false
|
|
282
|
+
});
|
|
283
|
+
if (reveal.revealed !== revealed) setReveal({
|
|
284
|
+
revealed,
|
|
285
|
+
changed: true
|
|
286
|
+
});
|
|
287
|
+
const lastError = useRef(error);
|
|
288
|
+
const [shake, setShake] = useState(void 0);
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
const previous = lastError.current;
|
|
291
|
+
lastError.current = error;
|
|
292
|
+
if (!error || error === previous) return;
|
|
293
|
+
setShake((current) => current === "a" ? "b" : "a");
|
|
294
|
+
}, [error]);
|
|
295
|
+
const summary = strength.level ? messages.summary({
|
|
296
|
+
label: strength.label,
|
|
297
|
+
met: strength.met.length,
|
|
298
|
+
total: rules.length
|
|
299
|
+
}) : "";
|
|
300
|
+
const disabled = Boolean(props.disabled);
|
|
301
|
+
const ariaInvalid = props["aria-invalid"];
|
|
302
|
+
const invalid = Boolean(error) || ariaInvalid !== void 0 && ariaInvalid !== false && ariaInvalid !== "false";
|
|
303
|
+
const describedBy = [field.controlProps["aria-describedby"] ?? props["aria-describedby"], rulesId].filter(Boolean).join(" ");
|
|
304
|
+
function toggleReveal() {
|
|
305
|
+
if (revealedProp === void 0) setRevealedInternal(!revealed);
|
|
306
|
+
onRevealedChange?.(!revealed);
|
|
307
|
+
}
|
|
308
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
309
|
+
"data-slot": "password-strength",
|
|
310
|
+
"data-level": strength.level,
|
|
311
|
+
className: "grid min-w-0 font-sans [--tone:var(--color-border)] data-[level=1]:[--tone:var(--color-destructive-strong)] data-[level=2]:[--tone:var(--color-warning-strong)] data-[level=3]:[--tone:var(--color-success-strong)] data-[level=4]:[--tone:var(--color-success-strong)]",
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ jsx(Label$1, {
|
|
314
|
+
htmlFor: field.controlId,
|
|
315
|
+
className: "mb-1.5",
|
|
316
|
+
children: label
|
|
317
|
+
}),
|
|
318
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
319
|
+
role: "presentation",
|
|
320
|
+
isDisabled: disabled,
|
|
321
|
+
isInvalid: invalid,
|
|
322
|
+
"data-shake": shake,
|
|
323
|
+
"data-slot": "password-strength-shell",
|
|
324
|
+
className: shellClass,
|
|
325
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
326
|
+
autoComplete: "new-password",
|
|
327
|
+
autoCapitalize: "off",
|
|
328
|
+
autoCorrect: "off",
|
|
329
|
+
spellCheck: false,
|
|
330
|
+
...props,
|
|
331
|
+
ref,
|
|
332
|
+
...field.controlProps,
|
|
333
|
+
type: revealed ? "text" : "password",
|
|
334
|
+
value,
|
|
335
|
+
onChange: (event) => {
|
|
336
|
+
const next = event.target.value;
|
|
337
|
+
if (valueProp === void 0) setInternal(next);
|
|
338
|
+
onChange?.(event);
|
|
339
|
+
onValueChange?.(next, estimateStrength(next, rules, options));
|
|
340
|
+
},
|
|
341
|
+
"aria-describedby": describedBy,
|
|
342
|
+
"data-slot": "password-strength-input",
|
|
343
|
+
"data-reveal": reveal.changed ? revealed ? "shown" : "hidden" : void 0,
|
|
344
|
+
className: cn(inputClass, className)
|
|
345
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
346
|
+
type: "button",
|
|
347
|
+
isDisabled: disabled,
|
|
348
|
+
"aria-label": messages.showPassword,
|
|
349
|
+
"aria-pressed": revealed,
|
|
350
|
+
"aria-controls": field.controlId,
|
|
351
|
+
onPress: toggleReveal,
|
|
352
|
+
className: toggleClass,
|
|
353
|
+
children: /* @__PURE__ */ jsx(EyeMorph, {
|
|
354
|
+
slashed: revealed,
|
|
355
|
+
slashPath: "M3 3l18 18"
|
|
356
|
+
})
|
|
357
|
+
})]
|
|
358
|
+
}),
|
|
359
|
+
/* @__PURE__ */ jsx(FieldMessage, {
|
|
360
|
+
id: field.errorId,
|
|
361
|
+
text: error,
|
|
362
|
+
tone: "error",
|
|
363
|
+
copy: "swap"
|
|
364
|
+
}),
|
|
365
|
+
/* @__PURE__ */ jsxs("div", {
|
|
366
|
+
className: "mt-3 flex items-center gap-3",
|
|
367
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
368
|
+
role: "meter",
|
|
369
|
+
"aria-label": messages.meterLabel,
|
|
370
|
+
"aria-valuemin": 0,
|
|
371
|
+
"aria-valuemax": 4,
|
|
372
|
+
"aria-valuenow": strength.level,
|
|
373
|
+
"aria-valuetext": strength.level ? strength.label : messages.noPassword,
|
|
374
|
+
className: "grid min-w-0 flex-1 grid-cols-4 gap-1.25",
|
|
375
|
+
children: [
|
|
376
|
+
0,
|
|
377
|
+
1,
|
|
378
|
+
2,
|
|
379
|
+
3
|
|
380
|
+
].map((index) => {
|
|
381
|
+
const on = index < strength.level;
|
|
382
|
+
const wave = on ? index - track.previousLevel : track.previousLevel - 1 - index;
|
|
383
|
+
const delay = Math.max(0, wave) * (on ? .05 : .03);
|
|
384
|
+
return /* @__PURE__ */ jsx("span", {
|
|
385
|
+
className: "relative isolate h-1 overflow-hidden rounded-full bg-muted",
|
|
386
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
387
|
+
"data-on": on || void 0,
|
|
388
|
+
style: { "--wave-delay": `${delay}s` },
|
|
389
|
+
className: "field-meter-fill absolute inset-0 rounded-[inherit]"
|
|
390
|
+
})
|
|
391
|
+
}, index);
|
|
392
|
+
})
|
|
393
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
394
|
+
"aria-hidden": "true",
|
|
395
|
+
className: "field-strength-word text-sm font-medium text-foreground",
|
|
396
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
397
|
+
swapKey: strength.level ? strength.label : "",
|
|
398
|
+
dir: track.direction,
|
|
399
|
+
children: strength.label
|
|
400
|
+
})
|
|
401
|
+
})]
|
|
402
|
+
}),
|
|
403
|
+
/* @__PURE__ */ jsx("ul", {
|
|
404
|
+
id: rulesId,
|
|
405
|
+
"aria-label": messages.rulesLabel,
|
|
406
|
+
className: "mt-4 grid list-none gap-1.5 p-0",
|
|
407
|
+
children: rules.map((rule) => {
|
|
408
|
+
const ok = met.has(rule.id);
|
|
409
|
+
const remaining = value && !ok ? rule.remaining?.(value) ?? 0 : 0;
|
|
410
|
+
const order = changed.indexOf(rule.id);
|
|
411
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
412
|
+
"data-met": ok || void 0,
|
|
413
|
+
style: { "--delay": `${order > 0 ? order * .05 : 0}s` },
|
|
414
|
+
className: "flex min-h-5 items-center gap-2.5 text-sm leading-(--leading-normal) text-muted-foreground data-[met]:text-foreground motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle",
|
|
415
|
+
children: [
|
|
416
|
+
/* @__PURE__ */ jsx(RuleMark, {}),
|
|
417
|
+
/* @__PURE__ */ jsxs("span", {
|
|
418
|
+
className: "min-w-0",
|
|
419
|
+
children: [rule.label, /* @__PURE__ */ jsx("span", {
|
|
420
|
+
className: "sr-only",
|
|
421
|
+
children: ok ? messages.ruleMet : messages.ruleNotMet
|
|
422
|
+
})]
|
|
423
|
+
}),
|
|
424
|
+
/* @__PURE__ */ jsx("span", {
|
|
425
|
+
"aria-hidden": "true",
|
|
426
|
+
className: "relative ms-auto ps-2 text-xs whitespace-nowrap text-muted-foreground",
|
|
427
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
428
|
+
swapKey: remaining > 0 ? "remaining" : "",
|
|
429
|
+
dir: 1,
|
|
430
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
431
|
+
className: "field-count-text",
|
|
432
|
+
children: [
|
|
433
|
+
messages.remainingPrefix ? `${messages.remainingPrefix} ` : null,
|
|
434
|
+
/* @__PURE__ */ jsx(RollingCount, { value: remaining }),
|
|
435
|
+
messages.remainingSuffix ? ` ${messages.remainingSuffix}` : null
|
|
436
|
+
]
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
]
|
|
441
|
+
}, rule.id);
|
|
442
|
+
})
|
|
443
|
+
}),
|
|
444
|
+
/* @__PURE__ */ jsx("span", {
|
|
445
|
+
className: "sr-only",
|
|
446
|
+
role: "status",
|
|
447
|
+
children: summary
|
|
448
|
+
})
|
|
449
|
+
]
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
//#endregion
|
|
453
|
+
export { PasswordStrength, countCodePoints, createPasswordRules, defaultPasswordRules, defaultPasswordStrengthMessages, estimateStrength };
|
|
454
|
+
|
|
455
|
+
//# sourceMappingURL=password-strength.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-strength.js","names":["Label","AriaGroup","AriaInput","AriaButton"],"sources":["../../src/password-strength/password-strength.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useCallback, useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n type InputProps as AriaInputProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { EyeMorph } from \"../lib/eye-morph.js\";\nimport { FieldMessage } from \"../lib/field-message.js\";\nimport { useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useLeavingItems,\n} from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { Label } from \"../label/label.js\";\n\nexport interface PasswordRule {\n id: string;\n label: string;\n test: (password: string) => boolean;\n /** Characters still missing. Shown as a rolling count beside the rule while it is unmet. */\n remaining?: (password: string) => number;\n}\n\nexport interface PasswordStrengthResult {\n level: 0 | 1 | 2 | 3 | 4;\n label: string;\n met: string[];\n /** True while the password is shorter than the short length, so the meter holds at its first step. */\n short?: boolean;\n}\n\n/** The strength words, one per meter step. Override them to translate the meter. */\nexport interface PasswordStrengthLevels {\n tooShort: string;\n weak: string;\n fair: string;\n good: string;\n strong: string;\n}\n\n/** Every user-facing string in PasswordStrength. All have English defaults; pass only the ones you change. */\nexport interface PasswordStrengthMessages {\n levels: PasswordStrengthLevels;\n /** Name of the show password toggle. It is a pressed toggle (aria-pressed), so the name stays the same in both states. */\n showPassword: string;\n /** Accessible name of the strength meter. */\n meterLabel: string;\n /** Meter value text while the field is empty. */\n noPassword: string;\n /** Accessible name of the requirements list. */\n rulesLabel: string;\n /** Read after a rule's label for screen readers, including any leading punctuation. */\n ruleMet: string;\n ruleNotMet: string;\n /** Words around the rolling count beside an unmet rule, as in \"4 more\". */\n remainingPrefix: string;\n remainingSuffix: string;\n /** The polite live-region summary read after each change. */\n summary: (result: { label: string; met: number; total: number }) => string;\n}\n\nexport const defaultPasswordStrengthMessages: PasswordStrengthMessages = {\n levels: { tooShort: \"Too short\", weak: \"Weak\", fair: \"Fair\", good: \"Good\", strong: \"Strong\" },\n showPassword: \"Show password\",\n meterLabel: \"Password strength\",\n noPassword: \"No password yet\",\n rulesLabel: \"Password requirements\",\n ruleMet: \", met\",\n ruleNotMet: \", not met\",\n remainingPrefix: \"\",\n remainingSuffix: \"more\",\n summary: ({ label, met, total }) => `Strength: ${label}. ${met} of ${total} requirements met.`,\n};\n\n/** Partial messages, with partial strength words, for the `messages` prop. */\nexport type PasswordStrengthMessagesInput = Partial<Omit<PasswordStrengthMessages, \"levels\">> & {\n levels?: Partial<PasswordStrengthLevels>;\n};\n\n/**\n * A new-password field that shows how strong the password is while it is typed:\n * four segments fill and change tone, each rule checks off with a drawn tick, and\n * the strength word swaps in place. Use it when creating or changing a password;\n * use `PasswordField` for sign in. Scoring happens on the device, nothing is sent\n * anywhere.\n *\n * Native input props apply to the input (`className` too); `disabled` also\n * disables the toggle, and `aria-invalid` gives the box its invalid border.\n *\n * @example\n * <PasswordStrength label=\"New password\" onValueChange={(value, strength) => save(value, strength.level)} />\n */\nexport interface PasswordStrengthProps extends Omit<\n AriaInputProps,\n \"className\" | \"style\" | \"size\" | \"type\" | \"value\" | \"defaultValue\" | \"children\"\n> {\n /** Extra classes for the input. Merged last, so they win conflicts with ours. */\n className?: string;\n /** The visible label, tied to the input with `htmlFor`. */\n label: string;\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string, strength: PasswordStrengthResult) => void;\n /** Rules to check. Strength is the share of rules met, spread over four steps. */\n rules?: PasswordRule[];\n /**\n * Error copy tied to the field. It is an alert, opens on a spring and swaps as a\n * whole when it changes; the field shakes once each time a new error appears.\n */\n error?: string;\n revealed?: boolean;\n onRevealedChange?: (revealed: boolean) => void;\n /** Translated or reworded copy. Every string has an English default; see PasswordStrengthMessages. */\n messages?: PasswordStrengthMessagesInput;\n /** Below this many characters the meter holds at its first step and reads \"Too short\". Defaults to 8. */\n shortLength?: number;\n /** How characters are counted for shortLength. Defaults to code points, so an emoji counts once; pass `password => password.length` to count UTF-16 units like a server might. */\n countCharacters?: (password: string) => number;\n}\n\n/** Counts code points, so an emoji or other astral character counts as one. */\nexport const countCodePoints = (password: string) => Array.from(password).length;\n\n/** Labels for the built-in rules. `length` receives the minimum length. */\nexport interface PasswordRuleLabels {\n length: (minLength: number) => string;\n case: string;\n number: string;\n symbol: string;\n}\n\nexport interface PasswordRuleOptions {\n /** Characters the length rule asks for. Defaults to 12. */\n minLength?: number;\n /** Translated or reworded rule labels. */\n labels?: Partial<PasswordRuleLabels>;\n /** How the length rule counts characters. Defaults to code points. */\n countCharacters?: (password: string) => number;\n}\n\nconst defaultRuleLabels: PasswordRuleLabels = {\n length: (minLength) => `At least ${minLength} characters`,\n case: \"Upper and lowercase letters\",\n number: \"At least one number\",\n symbol: \"At least one symbol\",\n};\n\n/** The built-in rules (length, mixed case, number, symbol) with your own minimum length, labels and character count. */\nexport function createPasswordRules({\n minLength = 12,\n labels,\n countCharacters = countCodePoints,\n}: PasswordRuleOptions = {}): PasswordRule[] {\n const copy = { ...defaultRuleLabels, ...labels };\n return [\n {\n id: \"length\",\n label: copy.length(minLength),\n test: (password) => countCharacters(password) >= minLength,\n remaining: (password) => Math.max(0, minLength - countCharacters(password)),\n },\n {\n id: \"case\",\n label: copy.case,\n test: (password) => /\\p{Ll}/u.test(password) && /\\p{Lu}/u.test(password),\n },\n { id: \"number\", label: copy.number, test: (password) => /\\p{N}/u.test(password) },\n { id: \"symbol\", label: copy.symbol, test: (password) => /[^\\p{L}\\p{N}\\s]/u.test(password) },\n ];\n}\n\nexport const defaultPasswordRules: PasswordRule[] = createPasswordRules();\n\nexport interface EstimateStrengthOptions {\n /** Below this many characters a password stays at the first step, however varied it is. Defaults to 8. */\n shortLength?: number;\n /** Strength words for the result's label. */\n levels?: Partial<PasswordStrengthLevels>;\n /** How characters are counted for shortLength. Defaults to code points. */\n countCharacters?: (password: string) => number;\n}\n\n/** Scores a password on the device: one step per share of rules met, capped at the first step while it is shorter than the short length (8 by default). */\nexport function estimateStrength(\n password: string,\n rules: PasswordRule[] = defaultPasswordRules,\n { shortLength = 8, levels, countCharacters = countCodePoints }: EstimateStrengthOptions = {},\n): PasswordStrengthResult {\n if (!password) return { level: 0, label: \"\", met: [] };\n const words = { ...defaultPasswordStrengthMessages.levels, ...levels };\n const met = rules.filter((rule) => rule.test(password)).map((rule) => rule.id);\n if (countCharacters(password) < shortLength)\n return { level: 1, label: words.tooShort, met, short: true };\n const level = Math.min(\n 4,\n Math.max(1, Math.round((met.length / Math.max(rules.length, 1)) * 4)),\n ) as 1 | 2 | 3 | 4;\n return {\n level,\n label: [words.weak, words.fair, words.good, words.strong][level - 1] ?? words.weak,\n met,\n };\n}\n\n/**\n * The field's box: a React Aria `Group` with the shared field surface (see\n * PasswordField for why a Group and for `data-focus-within`). Hover darkens the\n * border only to `muted-foreground`, as the reference does here, and a new error\n * shakes the box (two animation names, so each restart plays).\n */\nconst shellClass = cn(\n fieldSurfaceVariants(),\n \"flex items-center gap-1 h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [--field-toggle-size:calc(var(--spacing)*8.5)] [&:where([data-focus-within])]:border-foreground data-[hovered]:not-data-[disabled]:not-data-[focus-within]:not-data-[invalid]:border-muted-foreground motion-safe:data-[shake=a]:animate-field-shake-a motion-safe:data-[shake=b]:animate-field-shake-b\",\n);\n\nconst inputClass =\n \"min-w-0 flex-1 border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed motion-safe:data-[reveal=shown]:animate-field-reveal-shown motion-safe:data-[reveal=hidden]:animate-field-reveal-hidden\";\n\nconst toggleClass =\n \"field-toggle-button grid size-(--field-toggle-size) flex-none cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 text-muted-foreground [--press-scale:.94] [-webkit-tap-highlight-color:transparent] outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground aria-pressed:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\";\n\ninterface Place {\n /** Place value, so 10 to 9 lets the tens column close while the ones column turns. */\n key: number;\n digit: string;\n seq: number;\n}\n\nconst toPlaces = (value: number, seq: number, previous: Map<number, Place>): Place[] => {\n const digits = String(value).split(\"\");\n return digits.map((digit, index) => {\n const key = digits.length - 1 - index;\n return { key, digit, seq: previous.get(key)?.seq ?? seq };\n });\n};\n\nfunction LeavingDigit({\n id,\n rect,\n onDone,\n children,\n}: {\n id: number;\n rect: { left: number; top: number; width: number; height: number };\n onDone: (id: number) => void;\n children: string;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n style={rect}\n className=\"absolute motion-safe:animate-field-count-out motion-reduce:animate-fade-out\"\n >\n {children}\n </span>\n );\n}\n\nfunction PlaceColumn({\n place,\n direction,\n leaving,\n onDone,\n}: {\n place: Place;\n direction: number;\n leaving: boolean;\n onDone: (key: number) => void;\n}) {\n const digitRef = useRef<HTMLSpanElement>(null);\n const digits = useLeavingItems(place.digit, place.digit, () => digitRef.current);\n const done = useCallback(() => onDone(place.key), [onDone, place.key]);\n useExitBackstop(leaving, done, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) done();\n }, [leaving, done]);\n return (\n <span\n style={{ \"--roll\": direction } as CSSProperties}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"field-column-shrink\"\n )\n done();\n }}\n className={cn(\n \"field-number-column overflow-x-clip\",\n leaving\n ? \"motion-safe:animate-field-column-out\"\n : place.seq > 0 && \"motion-safe:animate-field-column-in\",\n )}\n >\n {digits.leaving.map((item) => (\n <LeavingDigit key={item.id} id={item.id} rect={item.rect} onDone={digits.remove}>\n {item.value}\n </LeavingDigit>\n ))}\n <span\n key={place.digit}\n ref={digitRef}\n className={\n digits.changed\n ? \"motion-safe:animate-field-count-in motion-reduce:animate-field-swap-fade-in\"\n : undefined\n }\n >\n {place.digit}\n </span>\n </span>\n );\n}\n\n/** The count beside an unmet rule. It rolls digit by digit: arriving from below when the count grew, from above when it fell, and a gained or lost digit opens or closes its column. */\nfunction RollingCount({ value }: { value: number }) {\n const [state, setState] = useState(() => ({\n value,\n direction: 1,\n seq: 0,\n places: toPlaces(value, 0, new Map<number, Place>()),\n gone: [] as Place[],\n }));\n let { places, gone, direction } = state;\n if (state.value !== value) {\n const seq = state.seq + 1;\n direction = value > state.value ? 1 : -1;\n places = toPlaces(value, seq, new Map(state.places.map((place) => [place.key, place])));\n const kept = new Set(places.map((place) => place.key));\n gone = [\n ...state.gone.filter((place) => !kept.has(place.key)),\n ...state.places.filter((place) => !kept.has(place.key)),\n ];\n setState({ value, direction, seq, places, gone });\n }\n const finish = useCallback(\n (key: number) =>\n setState((current) => ({\n ...current,\n gone: current.gone.filter((place) => place.key !== key),\n })),\n [],\n );\n const all = [\n ...places.map((place) => ({ place, leaving: false })),\n ...gone.map((place) => ({ place, leaving: true })),\n ].sort((a, b) => b.place.key - a.place.key);\n return (\n <span className=\"inline-flex tabular-nums\">\n {all.map(({ place, leaving }) => (\n <PlaceColumn\n key={place.key}\n place={place}\n direction={direction}\n leaving={leaving}\n onDone={finish}\n />\n ))}\n </span>\n );\n}\n\n/** One rule's tick: the tint grows under it while it draws from its short stroke; unchecking retracts it faster than it drew. */\nfunction RuleMark() {\n return (\n <span aria-hidden=\"true\" className=\"field-rule-mark\">\n <span className=\"field-rule-fill\" />\n <svg\n className=\"field-rule-glyph\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M4.75 8.25 7 10.5l4.25-4.75\" pathLength={1} className=\"field-rule-tick\" />\n </svg>\n </span>\n );\n}\n\n/**\n * A new-password input with a strength meter and a checklist (see\n * {@link PasswordStrengthProps}).\n */\nexport const PasswordStrength = forwardRef<HTMLInputElement, PasswordStrengthProps>(\n function PasswordStrength(\n {\n label,\n value: valueProp,\n defaultValue = \"\",\n onValueChange,\n onChange,\n rules = defaultPasswordRules,\n error,\n revealed: revealedProp,\n onRevealedChange,\n messages: messagesProp,\n shortLength,\n countCharacters,\n className,\n ...props\n },\n ref,\n ) {\n const messages: PasswordStrengthMessages = {\n ...defaultPasswordStrengthMessages,\n ...messagesProp,\n levels: { ...defaultPasswordStrengthMessages.levels, ...messagesProp?.levels },\n };\n const options: EstimateStrengthOptions = {\n ...(shortLength === undefined ? {} : { shortLength }),\n levels: messages.levels,\n ...(countCharacters === undefined ? {} : { countCharacters }),\n };\n const field = useFieldWiring({\n enabled: true,\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description: undefined,\n error,\n });\n const rulesId = `${field.controlId}-rules`;\n const [internal, setInternal] = useState(defaultValue);\n const value = valueProp ?? internal;\n const [revealedInternal, setRevealedInternal] = useState(false);\n const revealed = revealedProp ?? revealedInternal;\n const strength = estimateStrength(value, rules, options);\n const met = new Set(strength.met);\n\n // The previous level and rule states tell which way the word moves and which segments and ticks join a staggered wave.\n const rank = strength.short ? 0.5 : strength.level;\n const metKey = strength.met.join(\" \");\n const [track, setTrack] = useState({\n rank,\n level: strength.level,\n previousLevel: strength.level,\n direction: 1,\n metKey,\n previousMet: metKey,\n });\n if (track.rank !== rank || track.metKey !== metKey) {\n setTrack({\n rank,\n level: strength.level,\n previousLevel: track.rank !== rank ? track.level : track.previousLevel,\n direction: track.rank === rank ? track.direction : rank > track.rank ? 1 : -1,\n metKey,\n previousMet: track.metKey !== metKey ? track.metKey : track.previousMet,\n });\n }\n const previousMet = new Set(track.previousMet.split(\" \").filter(Boolean));\n const changed = rules\n .filter((rule) => met.has(rule.id) !== previousMet.has(rule.id))\n .map((rule) => rule.id);\n\n // Only a change after mount resolves the text, so the field never blurs in on first paint.\n const [reveal, setReveal] = useState({ revealed, changed: false });\n if (reveal.revealed !== revealed) setReveal({ revealed, changed: true });\n\n // A new error shakes the box once. The animation name alternates, held in state, so each one restarts it.\n const lastError = useRef(error);\n const [shake, setShake] = useState<\"a\" | \"b\" | undefined>(undefined);\n useEffect(() => {\n const previous = lastError.current;\n lastError.current = error;\n if (!error || error === previous) return;\n setShake((current) => (current === \"a\" ? \"b\" : \"a\"));\n }, [error]);\n\n // The live region speaks only when the level or the count of met rules changes, not on every keystroke.\n const summary = strength.level\n ? messages.summary({\n label: strength.label,\n met: strength.met.length,\n total: rules.length,\n })\n : \"\";\n\n const disabled = Boolean(props.disabled);\n const ariaInvalid = props[\"aria-invalid\"];\n const invalid =\n Boolean(error) ||\n (ariaInvalid !== undefined && ariaInvalid !== false && ariaInvalid !== \"false\");\n const describedBy = [\n field.controlProps[\"aria-describedby\"] ?? props[\"aria-describedby\"],\n rulesId,\n ]\n .filter(Boolean)\n .join(\" \");\n\n function toggleReveal() {\n if (revealedProp === undefined) setRevealedInternal(!revealed);\n onRevealedChange?.(!revealed);\n }\n\n return (\n <div\n data-slot=\"password-strength\"\n data-level={strength.level}\n className=\"grid min-w-0 font-sans [--tone:var(--color-border)] data-[level=1]:[--tone:var(--color-destructive-strong)] data-[level=2]:[--tone:var(--color-warning-strong)] data-[level=3]:[--tone:var(--color-success-strong)] data-[level=4]:[--tone:var(--color-success-strong)]\"\n >\n <Label htmlFor={field.controlId} className=\"mb-1.5\">\n {label}\n </Label>\n <AriaGroup\n role=\"presentation\"\n isDisabled={disabled}\n isInvalid={invalid}\n data-shake={shake}\n data-slot=\"password-strength-shell\"\n className={shellClass}\n >\n <AriaInput\n autoComplete=\"new-password\"\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n {...props}\n ref={ref}\n {...field.controlProps}\n type={revealed ? \"text\" : \"password\"}\n value={value}\n onChange={(event) => {\n const next = event.target.value;\n if (valueProp === undefined) setInternal(next);\n onChange?.(event);\n onValueChange?.(next, estimateStrength(next, rules, options));\n }}\n aria-describedby={describedBy}\n data-slot=\"password-strength-input\"\n data-reveal={reveal.changed ? (revealed ? \"shown\" : \"hidden\") : undefined}\n className={cn(inputClass, className)}\n />\n <AriaButton\n type=\"button\"\n isDisabled={disabled}\n aria-label={messages.showPassword}\n aria-pressed={revealed}\n aria-controls={field.controlId}\n onPress={toggleReveal}\n className={toggleClass}\n >\n <EyeMorph slashed={revealed} slashPath=\"M3 3l18 18\" />\n </AriaButton>\n </AriaGroup>\n <FieldMessage id={field.errorId} text={error} tone=\"error\" copy=\"swap\" />\n <div className=\"mt-3 flex items-center gap-3\">\n <div\n role=\"meter\"\n aria-label={messages.meterLabel}\n aria-valuemin={0}\n aria-valuemax={4}\n aria-valuenow={strength.level}\n aria-valuetext={strength.level ? strength.label : messages.noPassword}\n className=\"grid min-w-0 flex-1 grid-cols-4 gap-1.25\"\n >\n {[0, 1, 2, 3].map((index) => {\n const on = index < strength.level;\n // A pasted password fills left to right; clearing empties right to left.\n const wave = on ? index - track.previousLevel : track.previousLevel - 1 - index;\n const delay = Math.max(0, wave) * (on ? 0.05 : 0.03);\n return (\n <span\n key={index}\n className=\"relative isolate h-1 overflow-hidden rounded-full bg-muted\"\n >\n <span\n data-on={on || undefined}\n style={{ \"--wave-delay\": `${delay}s` } as CSSProperties}\n className=\"field-meter-fill absolute inset-0 rounded-[inherit]\"\n />\n </span>\n );\n })}\n </div>\n <span\n aria-hidden=\"true\"\n className=\"field-strength-word text-sm font-medium text-foreground\"\n >\n <SwapText swapKey={strength.level ? strength.label : \"\"} dir={track.direction}>\n {strength.label}\n </SwapText>\n </span>\n </div>\n <ul\n id={rulesId}\n aria-label={messages.rulesLabel}\n className=\"mt-4 grid list-none gap-1.5 p-0\"\n >\n {rules.map((rule) => {\n const ok = met.has(rule.id);\n const remaining = value && !ok ? (rule.remaining?.(value) ?? 0) : 0;\n const order = changed.indexOf(rule.id);\n return (\n <li\n key={rule.id}\n data-met={ok || undefined}\n style={{ \"--delay\": `${order > 0 ? order * 0.05 : 0}s` } as CSSProperties}\n className=\"flex min-h-5 items-center gap-2.5 text-sm leading-(--leading-normal) text-muted-foreground data-[met]:text-foreground motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle\"\n >\n <RuleMark />\n <span className=\"min-w-0\">\n {rule.label}\n <span className=\"sr-only\">{ok ? messages.ruleMet : messages.ruleNotMet}</span>\n </span>\n <span\n aria-hidden=\"true\"\n className=\"relative ms-auto ps-2 text-xs whitespace-nowrap text-muted-foreground\"\n >\n <SwapText swapKey={remaining > 0 ? \"remaining\" : \"\"} dir={1}>\n <span className=\"field-count-text\">\n {messages.remainingPrefix ? `${messages.remainingPrefix} ` : null}\n <RollingCount value={remaining} />\n {messages.remainingSuffix ? ` ${messages.remainingSuffix}` : null}\n </span>\n </SwapText>\n </span>\n </li>\n );\n })}\n </ul>\n <span className=\"sr-only\" role=\"status\">\n {summary}\n </span>\n </div>\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DialogTrigger as PopoverTrigger, PopoverProps } from "react-aria-components";
|
|
2
1
|
import { ReactNode } from "react";
|
|
2
|
+
import { DialogTrigger as PopoverTrigger, PopoverProps } from "react-aria-components";
|
|
3
3
|
//#region src/popover/popover.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Popover, ported from the legacy shadcn/Radix component.
|
|
@@ -43,8 +43,16 @@ interface PopoverProps$1 extends Omit<PopoverProps, "className" | "style" | "chi
|
|
|
43
43
|
* @default "bottom"
|
|
44
44
|
*/
|
|
45
45
|
placement?: PopoverProps["placement"];
|
|
46
|
+
/**
|
|
47
|
+
* How it comes in: `rise` grows out of the trigger (offset toward it, a little
|
|
48
|
+
* small, then settles on a spring) and leaves quicker; `none` leaves the entrance
|
|
49
|
+
* to the caller, as a picker that animates its own popover does.
|
|
50
|
+
*
|
|
51
|
+
* @default "rise"
|
|
52
|
+
*/
|
|
53
|
+
entrance?: "rise" | "none";
|
|
46
54
|
}
|
|
47
|
-
declare
|
|
55
|
+
declare const Popover$1: import("react").ForwardRefExoticComponent<PopoverProps$1 & import("react").RefAttributes<HTMLElement>>;
|
|
48
56
|
//#endregion
|
|
49
57
|
export { Popover$1 as Popover, PopoverProps$1 as PopoverProps, PopoverTrigger };
|
|
50
58
|
//# sourceMappingURL=popover.d.ts.map
|
package/dist/popover/popover.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { cnAll } from "../lib/cn.js";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import { forwardRef } from "react";
|
|
4
5
|
import { DialogTrigger as PopoverTrigger, Popover } from "react-aria-components";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/popover/popover.tsx
|
|
@@ -30,16 +31,17 @@ const popoverVariants = cva([
|
|
|
30
31
|
"box-border font-sans text-sm leading-(--leading-normal)",
|
|
31
32
|
"outline-none"
|
|
32
33
|
]);
|
|
33
|
-
|
|
34
|
+
const Popover$1 = forwardRef(function Popover$2({ children, className, placement = "bottom", offset = 4, entrance = "rise", ...props }, ref) {
|
|
34
35
|
return /* @__PURE__ */ jsx(Popover, {
|
|
36
|
+
ref,
|
|
35
37
|
...props,
|
|
36
38
|
placement,
|
|
37
39
|
offset,
|
|
38
40
|
"data-slot": "popover",
|
|
39
|
-
className:
|
|
41
|
+
className: cnAll(popoverVariants(), entrance === "rise" && "popover-motion", className),
|
|
40
42
|
children
|
|
41
43
|
});
|
|
42
|
-
}
|
|
44
|
+
});
|
|
43
45
|
//#endregion
|
|
44
46
|
export { Popover$1 as Popover, PopoverTrigger, popoverVariants };
|
|
45
47
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover.js","names":["Popover","AriaPopover"],"sources":["../../src/popover/popover.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport type
|
|
1
|
+
{"version":3,"file":"popover.js","names":["Popover","AriaPopover"],"sources":["../../src/popover/popover.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Popover as AriaPopover,\n type PopoverProps as AriaPopoverProps,\n} from \"react-aria-components\";\n\nimport { cnAll } from \"../lib/cn.js\";\n\n/**\n * Popover, ported from the legacy shadcn/Radix component.\n *\n * Census: 2 files, 2 uses. `asChild` on the trigger twice, `className`\n * twice, `align=\"start\"` once. `PopoverAnchor` is exported by the legacy\n * and imported by nobody, so it is not ported; neither are `side`,\n * `sideOffset`, `alignOffset`, `open` / `onOpenChange`, `modal`,\n * `collisionPadding` or `avoidCollisions` -- zero call sites each. Both\n * existing uses are uncontrolled.\n *\n * IT GAINS AN ACCESSIBLE NAME. The legacy renders `role=\"dialog\"` with no\n * `aria-label` and no `aria-labelledby`, so both call sites announce an\n * unnamed dialog. React Aria labels the popover from its trigger\n * automatically once the two are paired, which is the only way to build\n * one here.\n *\n * The surface is `card`: in the legacy theme `--popover` is byte-identical\n * to `--card` in both themes, so the role the migration plan proposed\n * already exists under another name.\n */\nexport const popoverVariants = cva([\n // w-72 (288px) is the legacy default width, as a spacing-scale step.\n \"w-72 rounded-md border-(length:--border-width) border-border p-4\",\n \"bg-card text-card-foreground shadow-floating\",\n // The reset in @seekalfred/styles is OPT-IN, so a consumer may load\n // none: an unstyled document renders text in a serif, and content-box\n // would add the 16px padding on top of the width.\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n // React Aria gives the popover tabindex=-1 and focuses it on open, so it\n // can take a focus ring it does not want.\n \"outline-none\",\n]);\n\n/**\n * Content anchored to a trigger.\n *\n * @example\n * <PopoverTrigger>\n * <Button>Pick a date</Button>\n * <Popover placement=\"bottom start\">...</Popover>\n * </PopoverTrigger>\n */\nexport interface PopoverProps\n extends Omit<AriaPopoverProps, \"className\" | \"style\" | \"children\"> {\n children?: ReactNode;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * Where it opens, as React Aria's single `placement` string. The legacy\n * split this across `side` and `align`; its one non-default call site is\n * `align=\"start\"`, which is `\"bottom start\"` here.\n *\n * @default \"bottom\"\n */\n placement?: AriaPopoverProps[\"placement\"];\n /**\n * How it comes in: `rise` grows out of the trigger (offset toward it, a little\n * small, then settles on a spring) and leaves quicker; `none` leaves the entrance\n * to the caller, as a picker that animates its own popover does.\n *\n * @default \"rise\"\n */\n entrance?: \"rise\" | \"none\";\n}\n\nexport const Popover = forwardRef<HTMLElement, PopoverProps>(function Popover(\n { children, className, placement = \"bottom\", offset = 4, entrance = \"rise\", ...props },\n ref,\n) {\n return (\n <AriaPopover\n ref={ref}\n {...props}\n placement={placement}\n offset={offset}\n data-slot=\"popover\"\n className={cnAll(popoverVariants(), entrance === \"rise\" && \"popover-motion\", className)}\n >\n {/*\n No inner Dialog. Wrapping the content in one is the common React\n Aria idiom and it was in the first draft here -- but deleting it\n broke no assertion, so it was doing nothing: a Popover under a\n DialogTrigger already gets role=\"dialog\", a focus scope, and\n aria-labelledby pointing at the trigger. The wrapper was one more\n element and one more className to explain.\n */}\n {children}\n </AriaPopover>\n );\n});\n\n/**\n * Pairs a trigger with a popover and owns the open state.\n *\n * React Aria's `DialogTrigger`, renamed for the migration: it takes the\n * trigger element directly as its first child, which is what `asChild` was\n * doing on both legacy call sites.\n */\nexport { DialogTrigger as PopoverTrigger } from \"react-aria-components\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,kBAAkB,IAAI;CAEjC;CACA;CAIA;CAGA;AACF,CAAC;AAkCD,MAAaA,YAAU,WAAsC,SAASA,UACpE,EAAE,UAAU,WAAW,YAAY,UAAU,SAAS,GAAG,WAAW,QAAQ,GAAG,SAC/E,KACA;CACA,OACE,oBAACC,SAAD;EACO;EACL,GAAI;EACO;EACH;EACR,aAAU;EACV,WAAW,MAAM,gBAAgB,GAAG,aAAa,UAAU,kBAAkB,SAAS;EAUrF;CACU,CAAA;AAEjB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
|
-
import { ProgressBarProps } from "react-aria-components";
|
|
3
2
|
import { ReactNode } from "react";
|
|
3
|
+
import { ProgressBarProps } from "react-aria-components";
|
|
4
4
|
//#region src/progress/progress.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Progress, ported from the legacy shadcn/Radix component.
|
|
@@ -77,6 +77,6 @@ export interface ProgressProps extends Omit<ProgressBarProps, "className" | "sty
|
|
|
77
77
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
78
78
|
className?: string;
|
|
79
79
|
}
|
|
80
|
-
export declare
|
|
80
|
+
export declare const Progress: import("react").ForwardRefExoticComponent<ProgressProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
81
81
|
//#endregion
|
|
82
82
|
//# sourceMappingURL=progress.d.ts.map
|