@teamofsilicons/silicon-ui 0.0.0-stage → 0.1.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/LICENSE +22 -0
- package/README.md +63 -2
- package/components/accordion/accordion.d.ts +13 -0
- package/components/accordion/accordion.js +35 -0
- package/components/accordion/accordion.module.css +15 -0
- package/components/action-button/action-button.d.ts +11 -0
- package/components/action-button/action-button.js +57 -0
- package/components/action-button/action-button.module.css +15 -0
- package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
- package/components/activity-heatmap/activity-heatmap.js +303 -0
- package/components/activity-heatmap/activity-heatmap.module.css +83 -0
- package/components/alert/alert.d.ts +13 -0
- package/components/alert/alert.js +79 -0
- package/components/alert/alert.module.css +15 -0
- package/components/animated-counter/animated-counter.d.ts +13 -0
- package/components/animated-counter/animated-counter.js +73 -0
- package/components/animated-counter/animated-counter.module.css +12 -0
- package/components/announcement-bar/announcement-bar.d.ts +53 -0
- package/components/announcement-bar/announcement-bar.js +187 -0
- package/components/announcement-bar/announcement-bar.module.css +60 -0
- package/components/avatar/avatar.d.ts +8 -0
- package/components/avatar/avatar.js +18 -0
- package/components/avatar/avatar.module.css +60 -0
- package/components/avatar-group/avatar-group.d.ts +13 -0
- package/components/avatar-group/avatar-group.js +24 -0
- package/components/avatar-group/avatar-group.module.css +27 -0
- package/components/badge/badge.d.ts +10 -0
- package/components/badge/badge.js +66 -0
- package/components/badge/badge.module.css +52 -0
- package/components/bar-chart/bar-chart.d.ts +32 -0
- package/components/bar-chart/bar-chart.js +327 -0
- package/components/bar-chart/bar-chart.module.css +49 -0
- package/components/billing-toggle/billing-toggle.d.ts +35 -0
- package/components/billing-toggle/billing-toggle.js +83 -0
- package/components/billing-toggle/billing-toggle.module.css +139 -0
- package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
- package/components/bottom-sheet/bottom-sheet.js +386 -0
- package/components/bottom-sheet/bottom-sheet.module.css +69 -0
- package/components/breadcrumb/breadcrumb.d.ts +13 -0
- package/components/breadcrumb/breadcrumb.js +19 -0
- package/components/breadcrumb/breadcrumb.module.css +12 -0
- package/components/brush-chart/brush-chart.d.ts +45 -0
- package/components/brush-chart/brush-chart.js +426 -0
- package/components/brush-chart/brush-chart.module.css +69 -0
- package/components/button/button.d.ts +10 -0
- package/components/button/button.js +97 -0
- package/components/button/button.module.css +39 -0
- package/components/button-group/button-group.d.ts +59 -0
- package/components/button-group/button-group.js +280 -0
- package/components/button-group/button-group.module.css +87 -0
- package/components/button-link/button-link.d.ts +24 -0
- package/components/button-link/button-link.js +11 -0
- package/components/button-link/button-link.module.css +7 -0
- package/components/calendar/calendar.d.ts +22 -0
- package/components/calendar/calendar.js +253 -0
- package/components/calendar/calendar.module.css +51 -0
- package/components/card/card.d.ts +19 -0
- package/components/card/card.js +108 -0
- package/components/card/card.module.css +51 -0
- package/components/card-stack/card-stack.d.ts +34 -0
- package/components/card-stack/card-stack.js +291 -0
- package/components/card-stack/card-stack.module.css +63 -0
- package/components/carousel/carousel.d.ts +28 -0
- package/components/carousel/carousel.js +332 -0
- package/components/carousel/carousel.module.css +42 -0
- package/components/chat-thread/chat-thread.d.ts +135 -0
- package/components/chat-thread/chat-thread.js +406 -0
- package/components/chat-thread/chat-thread.module.css +242 -0
- package/components/checkbox/checkbox.d.ts +7 -0
- package/components/checkbox/checkbox.js +23 -0
- package/components/checkbox/checkbox.module.css +15 -0
- package/components/chip-group/chip-group.d.ts +24 -0
- package/components/chip-group/chip-group.js +196 -0
- package/components/chip-group/chip-group.module.css +38 -0
- package/components/code-block/code-block.d.ts +11 -0
- package/components/code-block/code-block.js +133 -0
- package/components/code-block/code-block.module.css +43 -0
- package/components/color-picker/color-picker.d.ts +40 -0
- package/components/color-picker/color-picker.js +449 -0
- package/components/color-picker/color-picker.module.css +111 -0
- package/components/combobox/combobox.d.ts +19 -0
- package/components/combobox/combobox.js +167 -0
- package/components/combobox/combobox.module.css +28 -0
- package/components/command-palette/command-palette.d.ts +20 -0
- package/components/command-palette/command-palette.js +151 -0
- package/components/command-palette/command-palette.module.css +94 -0
- package/components/comment-thread/comment-thread.d.ts +120 -0
- package/components/comment-thread/comment-thread.js +343 -0
- package/components/comment-thread/comment-thread.module.css +195 -0
- package/components/confirm-morph/confirm-morph.d.ts +65 -0
- package/components/confirm-morph/confirm-morph.js +355 -0
- package/components/confirm-morph/confirm-morph.module.css +146 -0
- package/components/context-menu/context-menu.d.ts +18 -0
- package/components/context-menu/context-menu.js +148 -0
- package/components/context-menu/context-menu.module.css +17 -0
- package/components/copy-button/copy-button.d.ts +10 -0
- package/components/copy-button/copy-button.js +69 -0
- package/components/copy-button/copy-button.module.css +21 -0
- package/components/countdown/countdown.d.ts +31 -0
- package/components/countdown/countdown.js +177 -0
- package/components/countdown/countdown.module.css +49 -0
- package/components/date-picker/date-picker.d.ts +17 -0
- package/components/date-picker/date-picker.js +100 -0
- package/components/date-picker/date-picker.module.css +34 -0
- package/components/date-range-picker/date-range-picker.d.ts +44 -0
- package/components/date-range-picker/date-range-picker.js +534 -0
- package/components/date-range-picker/date-range-picker.module.css +214 -0
- package/components/dialog/dialog.d.ts +11 -0
- package/components/dialog/dialog.js +52 -0
- package/components/dialog/dialog.module.css +36 -0
- package/components/donut-chart/donut-chart.d.ts +46 -0
- package/components/donut-chart/donut-chart.js +382 -0
- package/components/donut-chart/donut-chart.module.css +48 -0
- package/components/drawer/drawer.d.ts +20 -0
- package/components/drawer/drawer.js +111 -0
- package/components/drawer/drawer.module.css +97 -0
- package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
- package/components/dropdown-menu/dropdown-menu.js +39 -0
- package/components/dropdown-menu/dropdown-menu.module.css +38 -0
- package/components/elastic-slider/elastic-slider.d.ts +39 -0
- package/components/elastic-slider/elastic-slider.js +261 -0
- package/components/elastic-slider/elastic-slider.module.css +40 -0
- package/components/empty-state/empty-state.d.ts +12 -0
- package/components/empty-state/empty-state.js +69 -0
- package/components/empty-state/empty-state.module.css +13 -0
- package/components/expandable-card/expandable-card.d.ts +13 -0
- package/components/expandable-card/expandable-card.js +89 -0
- package/components/expandable-card/expandable-card.module.css +22 -0
- package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
- package/components/expanding-button-group/expanding-button-group.js +258 -0
- package/components/expanding-button-group/expanding-button-group.module.css +44 -0
- package/components/expanding-search/expanding-search.d.ts +38 -0
- package/components/expanding-search/expanding-search.js +242 -0
- package/components/expanding-search/expanding-search.module.css +52 -0
- package/components/file-dropzone/file-dropzone.d.ts +44 -0
- package/components/file-dropzone/file-dropzone.js +379 -0
- package/components/file-dropzone/file-dropzone.module.css +115 -0
- package/components/file-upload/file-upload.d.ts +22 -0
- package/components/file-upload/file-upload.js +192 -0
- package/components/file-upload/file-upload.module.css +49 -0
- package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
- package/components/filter-toolbar/filter-toolbar.js +446 -0
- package/components/filter-toolbar/filter-toolbar.module.css +70 -0
- package/components/floating-button-group/floating-button-group.d.ts +51 -0
- package/components/floating-button-group/floating-button-group.js +215 -0
- package/components/floating-button-group/floating-button-group.module.css +74 -0
- package/components/gauge/gauge.d.ts +18 -0
- package/components/gauge/gauge.js +101 -0
- package/components/gauge/gauge.module.css +22 -0
- package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
- package/components/hold-to-confirm/hold-to-confirm.js +165 -0
- package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
- package/components/hover-card/hover-card.d.ts +38 -0
- package/components/hover-card/hover-card.js +145 -0
- package/components/hover-card/hover-card.module.css +20 -0
- package/components/image-compare/image-compare.d.ts +27 -0
- package/components/image-compare/image-compare.js +267 -0
- package/components/image-compare/image-compare.module.css +25 -0
- package/components/in-view-title/in-view-title.d.ts +17 -0
- package/components/in-view-title/in-view-title.js +70 -0
- package/components/in-view-title/in-view-title.module.css +13 -0
- package/components/inline-edit/inline-edit.d.ts +27 -0
- package/components/inline-edit/inline-edit.js +315 -0
- package/components/inline-edit/inline-edit.module.css +73 -0
- package/components/input/input.d.ts +7 -0
- package/components/input/input.js +60 -0
- package/components/input/input.module.css +22 -0
- package/components/json-viewer/json-viewer.d.ts +48 -0
- package/components/json-viewer/json-viewer.js +463 -0
- package/components/json-viewer/json-viewer.module.css +92 -0
- package/components/line-chart/line-chart.d.ts +58 -0
- package/components/line-chart/line-chart.js +383 -0
- package/components/line-chart/line-chart.module.css +60 -0
- package/components/mention-input/mention-input.d.ts +72 -0
- package/components/mention-input/mention-input.js +417 -0
- package/components/mention-input/mention-input.module.css +134 -0
- package/components/metric-card/metric-card.d.ts +8 -0
- package/components/metric-card/metric-card.js +44 -0
- package/components/metric-card/metric-card.module.css +18 -0
- package/components/money-input/money-input.d.ts +48 -0
- package/components/money-input/money-input.js +503 -0
- package/components/money-input/money-input.module.css +77 -0
- package/components/morph-select/morph-select.d.ts +50 -0
- package/components/morph-select/morph-select.js +395 -0
- package/components/morph-select/morph-select.module.css +88 -0
- package/components/multi-select/multi-select.d.ts +18 -0
- package/components/multi-select/multi-select.js +105 -0
- package/components/multi-select/multi-select.module.css +37 -0
- package/components/notification-center/notification-center.d.ts +24 -0
- package/components/notification-center/notification-center.js +146 -0
- package/components/notification-center/notification-center.module.css +79 -0
- package/components/number-field/number-field.d.ts +41 -0
- package/components/number-field/number-field.js +455 -0
- package/components/number-field/number-field.module.css +71 -0
- package/components/otp-input/otp-input.d.ts +13 -0
- package/components/otp-input/otp-input.js +129 -0
- package/components/otp-input/otp-input.module.css +28 -0
- package/components/pagination/pagination.d.ts +7 -0
- package/components/pagination/pagination.js +60 -0
- package/components/pagination/pagination.module.css +16 -0
- package/components/password-field/password-field.d.ts +14 -0
- package/components/password-field/password-field.js +51 -0
- package/components/password-field/password-field.module.css +27 -0
- package/components/password-strength/password-strength.d.ts +107 -0
- package/components/password-strength/password-strength.js +161 -0
- package/components/password-strength/password-strength.module.css +57 -0
- package/components/phone-input/phone-input.d.ts +73 -0
- package/components/phone-input/phone-input.js +612 -0
- package/components/phone-input/phone-input.module.css +98 -0
- package/components/popover/popover.d.ts +6 -0
- package/components/popover/popover.js +19 -0
- package/components/popover/popover.module.css +32 -0
- package/components/progress/progress.d.ts +8 -0
- package/components/progress/progress.js +42 -0
- package/components/progress/progress.module.css +1 -0
- package/components/radio-cards/radio-cards.d.ts +35 -0
- package/components/radio-cards/radio-cards.js +110 -0
- package/components/radio-cards/radio-cards.module.css +84 -0
- package/components/radio-group/radio-group.d.ts +13 -0
- package/components/radio-group/radio-group.js +55 -0
- package/components/radio-group/radio-group.module.css +30 -0
- package/components/resizable-panels/resizable-panels.d.ts +37 -0
- package/components/resizable-panels/resizable-panels.js +338 -0
- package/components/resizable-panels/resizable-panels.module.css +40 -0
- package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
- package/components/rich-text-editor/rich-text-editor.js +1733 -0
- package/components/rich-text-editor/rich-text-editor.module.css +201 -0
- package/components/ridgeline/ridgeline.d.ts +37 -0
- package/components/ridgeline/ridgeline.js +346 -0
- package/components/ridgeline/ridgeline.module.css +58 -0
- package/components/scroll-area/scroll-area.d.ts +40 -0
- package/components/scroll-area/scroll-area.js +153 -0
- package/components/scroll-area/scroll-area.module.css +79 -0
- package/components/search-field/search-field.d.ts +7 -0
- package/components/search-field/search-field.js +20 -0
- package/components/search-field/search-field.module.css +17 -0
- package/components/segmented-control/segmented-control.d.ts +16 -0
- package/components/segmented-control/segmented-control.js +56 -0
- package/components/segmented-control/segmented-control.module.css +15 -0
- package/components/select/select.d.ts +15 -0
- package/components/select/select.js +36 -0
- package/components/select/select.module.css +35 -0
- package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
- package/components/shortcut-recorder/shortcut-recorder.js +380 -0
- package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
- package/components/signature-pad/signature-pad.d.ts +72 -0
- package/components/signature-pad/signature-pad.js +450 -0
- package/components/signature-pad/signature-pad.module.css +84 -0
- package/components/skeleton/skeleton.d.ts +13 -0
- package/components/skeleton/skeleton.js +53 -0
- package/components/skeleton/skeleton.module.css +11 -0
- package/components/slider/slider.d.ts +45 -0
- package/components/slider/slider.js +347 -0
- package/components/slider/slider.module.css +45 -0
- package/components/slope-chart/slope-chart.d.ts +38 -0
- package/components/slope-chart/slope-chart.js +183 -0
- package/components/slope-chart/slope-chart.module.css +51 -0
- package/components/slot-text/slot-text.d.ts +26 -0
- package/components/slot-text/slot-text.js +119 -0
- package/components/slot-text/slot-text.module.css +19 -0
- package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
- package/components/sortable-data-table/sortable-data-table.js +240 -0
- package/components/sortable-data-table/sortable-data-table.module.css +116 -0
- package/components/sparkline/sparkline.d.ts +19 -0
- package/components/sparkline/sparkline.js +239 -0
- package/components/sparkline/sparkline.module.css +26 -0
- package/components/split-button/split-button.d.ts +19 -0
- package/components/split-button/split-button.js +33 -0
- package/components/split-button/split-button.module.css +51 -0
- package/components/stepper/stepper.d.ts +36 -0
- package/components/stepper/stepper.js +108 -0
- package/components/stepper/stepper.module.css +74 -0
- package/components/streamgraph/streamgraph.d.ts +51 -0
- package/components/streamgraph/streamgraph.js +376 -0
- package/components/streamgraph/streamgraph.module.css +55 -0
- package/components/swipe-actions/swipe-actions.d.ts +35 -0
- package/components/swipe-actions/swipe-actions.js +330 -0
- package/components/swipe-actions/swipe-actions.module.css +64 -0
- package/components/switch/switch.d.ts +7 -0
- package/components/switch/switch.js +45 -0
- package/components/switch/switch.module.css +12 -0
- package/components/tabs/tabs.d.ts +8 -0
- package/components/tabs/tabs.js +138 -0
- package/components/tabs/tabs.module.css +27 -0
- package/components/tag-input/tag-input.d.ts +15 -0
- package/components/tag-input/tag-input.js +150 -0
- package/components/tag-input/tag-input.module.css +34 -0
- package/components/text-morph/text-morph.d.ts +13 -0
- package/components/text-morph/text-morph.js +69 -0
- package/components/text-morph/text-morph.module.css +8 -0
- package/components/text-reveal/text-reveal.d.ts +14 -0
- package/components/text-reveal/text-reveal.js +20 -0
- package/components/text-reveal/text-reveal.module.css +13 -0
- package/components/text-shimmer/text-shimmer.d.ts +18 -0
- package/components/text-shimmer/text-shimmer.js +90 -0
- package/components/text-shimmer/text-shimmer.module.css +9 -0
- package/components/textarea/textarea.d.ts +7 -0
- package/components/textarea/textarea.js +56 -0
- package/components/textarea/textarea.module.css +22 -0
- package/components/theme-switch/theme-switch.d.ts +11 -0
- package/components/theme-switch/theme-switch.js +37 -0
- package/components/theme-switch/theme-switch.module.css +31 -0
- package/components/time-picker/time-picker.d.ts +13 -0
- package/components/time-picker/time-picker.js +95 -0
- package/components/time-picker/time-picker.module.css +23 -0
- package/components/timeline/timeline.d.ts +47 -0
- package/components/timeline/timeline.js +171 -0
- package/components/timeline/timeline.module.css +51 -0
- package/components/toast/toast.d.ts +8 -0
- package/components/toast/toast.js +118 -0
- package/components/toast/toast.module.css +100 -0
- package/components/toast-stack/toast-stack.d.ts +57 -0
- package/components/toast-stack/toast-stack.js +399 -0
- package/components/toast-stack/toast-stack.module.css +61 -0
- package/components/tooltip/tooltip.d.ts +8 -0
- package/components/tooltip/tooltip.js +62 -0
- package/components/tooltip/tooltip.module.css +18 -0
- package/components/tree-view/tree-view.d.ts +17 -0
- package/components/tree-view/tree-view.js +147 -0
- package/components/tree-view/tree-view.module.css +22 -0
- package/components/treemap/treemap.d.ts +35 -0
- package/components/treemap/treemap.js +375 -0
- package/components/treemap/treemap.module.css +59 -0
- package/components/usage-meter/usage-meter.d.ts +24 -0
- package/components/usage-meter/usage-meter.js +296 -0
- package/components/usage-meter/usage-meter.module.css +61 -0
- package/components/user-menu/user-menu.d.ts +62 -0
- package/components/user-menu/user-menu.js +316 -0
- package/components/user-menu/user-menu.module.css +97 -0
- package/components/waffle-chart/waffle-chart.d.ts +39 -0
- package/components/waffle-chart/waffle-chart.js +244 -0
- package/components/waffle-chart/waffle-chart.module.css +52 -0
- package/components.json +1330 -0
- package/lib/escape.d.ts +19 -0
- package/lib/escape.js +50 -0
- package/lib/media.d.ts +353 -0
- package/lib/media.js +65 -0
- package/lib/menu-highlight.d.ts +28 -0
- package/lib/menu-highlight.js +43 -0
- package/lib/motion-tokens.d.ts +60 -0
- package/lib/motion-tokens.js +25 -0
- package/lib/return-focus.d.ts +9 -0
- package/lib/return-focus.js +53 -0
- package/lib/silicon-provider.d.ts +60 -0
- package/lib/silicon-provider.js +47 -0
- package/lib/use-copy-feedback.d.ts +8 -0
- package/lib/use-copy-feedback.js +36 -0
- package/lib/use-today.d.ts +2 -0
- package/lib/use-today.js +29 -0
- package/package.json +66 -4
- package/styles.css +218 -0
- package/styles.css.d.ts +1 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { AnimatePresence, animate, motion, useMotionValue, useReducedMotion } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import styles from "./password-strength.module.css";
|
|
7
|
+
export const defaultPasswordStrengthMessages = {
|
|
8
|
+
levels: { tooShort: "Too short", weak: "Weak", fair: "Fair", good: "Good", strong: "Strong" },
|
|
9
|
+
showPassword: "Show password",
|
|
10
|
+
meterLabel: "Password strength",
|
|
11
|
+
noPassword: "No password yet",
|
|
12
|
+
rulesLabel: "Password requirements",
|
|
13
|
+
ruleMet: ", met",
|
|
14
|
+
ruleNotMet: ", not met",
|
|
15
|
+
remainingPrefix: "",
|
|
16
|
+
remainingSuffix: "more",
|
|
17
|
+
summary: ({ label, met, total }) => `Strength: ${label}. ${met} of ${total} requirements met.`,
|
|
18
|
+
};
|
|
19
|
+
/** Counts code points, so an emoji or other astral character counts as one. */
|
|
20
|
+
export const countCodePoints = (password) => Array.from(password).length;
|
|
21
|
+
const defaultRuleLabels = {
|
|
22
|
+
length: minLength => `At least ${minLength} characters`,
|
|
23
|
+
case: "Upper and lowercase letters",
|
|
24
|
+
number: "At least one number",
|
|
25
|
+
symbol: "At least one symbol",
|
|
26
|
+
};
|
|
27
|
+
/** The built-in rules (length, mixed case, number, symbol) with your own minimum length, labels and character count. */
|
|
28
|
+
export function createPasswordRules({ minLength = 12, labels, countCharacters = countCodePoints } = {}) {
|
|
29
|
+
const copy = { ...defaultRuleLabels, ...labels };
|
|
30
|
+
return [
|
|
31
|
+
{ id: "length", label: copy.length(minLength), test: password => countCharacters(password) >= minLength, remaining: password => Math.max(0, minLength - countCharacters(password)) },
|
|
32
|
+
{ id: "case", label: copy.case, test: password => /\p{Ll}/u.test(password) && /\p{Lu}/u.test(password) },
|
|
33
|
+
{ id: "number", label: copy.number, test: password => /\p{N}/u.test(password) },
|
|
34
|
+
{ id: "symbol", label: copy.symbol, test: password => /[^\p{L}\p{N}\s]/u.test(password) },
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
export const defaultPasswordRules = createPasswordRules();
|
|
38
|
+
/** 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). */
|
|
39
|
+
export function estimateStrength(password, rules = defaultPasswordRules, { shortLength = 8, levels, countCharacters = countCodePoints } = {}) {
|
|
40
|
+
if (!password)
|
|
41
|
+
return { level: 0, label: "", met: [] };
|
|
42
|
+
const words = { ...defaultPasswordStrengthMessages.levels, ...levels };
|
|
43
|
+
const met = rules.filter(rule => rule.test(password)).map(rule => rule.id);
|
|
44
|
+
if (countCharacters(password) < shortLength)
|
|
45
|
+
return { level: 1, label: words.tooShort, met, short: true };
|
|
46
|
+
const level = Math.min(4, Math.max(1, Math.round((met.length / Math.max(rules.length, 1)) * 4)));
|
|
47
|
+
return { level, label: [words.weak, words.fair, words.good, words.strong][level - 1] ?? words.weak, met };
|
|
48
|
+
}
|
|
49
|
+
const enter = [...motionTokens.ease.enter];
|
|
50
|
+
const standard = [...motionTokens.ease.standard];
|
|
51
|
+
const subscribe = () => () => { };
|
|
52
|
+
/** False on the server and during hydration, so reduced motion never changes the first client render. */
|
|
53
|
+
const useHydrated = () => useSyncExternalStore(subscribe, () => true, () => false);
|
|
54
|
+
/** The word rises when strength improves and drops when it falls, so the direction reads without looking at the meter. */
|
|
55
|
+
const rise = {
|
|
56
|
+
enter: (direction) => ({ opacity: 0, y: `${.3 * direction}em`, filter: `blur(${motionTokens.blur.soft}px)` }),
|
|
57
|
+
center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { duration: motionTokens.duration.standard, ease: enter } },
|
|
58
|
+
exit: (direction) => ({ opacity: 0, y: `${-.3 * direction}em`, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.fast, ease: standard } }),
|
|
59
|
+
};
|
|
60
|
+
const fade = { enter: { opacity: 0 }, center: { opacity: 1, transition: { duration: motionTokens.duration.fast } }, exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } } };
|
|
61
|
+
/** Digits turn like a counter: a shrinking count drops in from above, a growing one rises from below. */
|
|
62
|
+
const roll = {
|
|
63
|
+
enter: (direction) => ({ opacity: 0, y: `${.45 * direction}em`, filter: `blur(${motionTokens.blur.subtle}px)` }),
|
|
64
|
+
center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { y: motionTokens.spring.snappy, opacity: { duration: motionTokens.duration.fast }, filter: { duration: motionTokens.duration.fast } } },
|
|
65
|
+
exit: (direction) => ({ opacity: 0, y: `${-.45 * direction}em`, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.fast, ease: standard } }),
|
|
66
|
+
};
|
|
67
|
+
function RollingNumber({ value, reduced }) {
|
|
68
|
+
const [track, setTrack] = useState({ value, direction: 1 });
|
|
69
|
+
if (track.value !== value)
|
|
70
|
+
setTrack({ value, direction: value > track.value ? 1 : -1 });
|
|
71
|
+
const digits = String(value).split("");
|
|
72
|
+
// Columns are keyed by place value, so 10 → 9 lets the tens column narrow away while the ones column turns.
|
|
73
|
+
return _jsx("span", { className: styles.number, children: _jsx(AnimatePresence, { initial: false, custom: track.direction, children: digits.map((digit, index) => _jsx(motion.span, { className: styles.place, initial: { width: 0, opacity: 0 }, animate: { width: "auto", opacity: 1 }, exit: { width: 0, opacity: 0 }, transition: reduced ? { duration: 0 } : { width: motionTokens.spring.morph, opacity: { duration: motionTokens.duration.fast } }, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, custom: track.direction, children: _jsx(motion.span, { className: styles.digit, custom: track.direction, variants: reduced ? fade : roll, initial: "enter", animate: "center", exit: "exit", children: digit }, digit) }) }, digits.length - 1 - index)) }) });
|
|
74
|
+
}
|
|
75
|
+
/** One eye that a slash draws across, cutting the outline beneath it, instead of swapping two icons. */
|
|
76
|
+
function EyeMorph({ slashed, reduced }) {
|
|
77
|
+
const maskId = `eye-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
78
|
+
const slash = { pathLength: slashed ? 1 : 0, opacity: slashed ? 1 : 0 };
|
|
79
|
+
const transition = reduced ? { duration: 0 } : { pathLength: { duration: motionTokens.duration.standard, ease: standard }, opacity: { duration: motionTokens.duration.instant } };
|
|
80
|
+
return _jsxs("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsxs("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "24", height: "24", children: [_jsx("rect", { width: "24", height: "24", fill: "white", stroke: "none" }), _jsx(motion.path, { d: "M3 3l18 18", stroke: "black", strokeWidth: 5, initial: false, animate: slash, transition: transition })] }), _jsxs("g", { mask: `url(#${maskId})`, children: [_jsx("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }), _jsx("circle", { cx: "12", cy: "12", r: "3" })] }), _jsx(motion.path, { d: "M3 3l18 18", initial: false, animate: slash, transition: transition })] });
|
|
81
|
+
}
|
|
82
|
+
/** The tint grows under the tick while the tick draws from its short stroke; unchecking retracts it faster than it drew. */
|
|
83
|
+
function RuleMark({ met, delay, reduced }) {
|
|
84
|
+
return _jsxs("span", { className: styles.mark, "aria-hidden": "true", children: [_jsx(motion.span, { className: styles.markFill, initial: false, animate: { scale: met ? 1 : .5, opacity: met ? 1 : 0 }, transition: reduced ? { duration: 0 } : { scale: { ...motionTokens.spring.snappy, delay }, opacity: { duration: met ? motionTokens.duration.fast : motionTokens.duration.instant, delay } } }), _jsx("svg", { className: styles.tick, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx(motion.path, { d: "M4.75 8.25 7 10.5l4.25-4.75", initial: false, animate: { pathLength: met ? 1 : 0, opacity: met ? 1 : 0 }, transition: reduced ? { duration: 0 } : met ? { pathLength: { duration: motionTokens.duration.standard, ease: enter, delay: delay + .06 }, opacity: { duration: .05, delay: delay + .06 } } : { pathLength: { duration: motionTokens.duration.fast, ease: standard }, opacity: { duration: motionTokens.duration.fast, delay: .06 } } }) })] });
|
|
85
|
+
}
|
|
86
|
+
/** Error copy opens its row on a spring, then the words settle in. The row follows the measured copy, so a wrap never snaps. */
|
|
87
|
+
function ErrorRow({ id, text, reduced }) {
|
|
88
|
+
const copy = useRef(null);
|
|
89
|
+
const [height, setHeight] = useState("auto");
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const node = copy.current;
|
|
92
|
+
if (!node || typeof ResizeObserver === "undefined")
|
|
93
|
+
return;
|
|
94
|
+
const observer = new ResizeObserver(([entry]) => { if (entry)
|
|
95
|
+
setHeight(entry.borderBoxSize?.[0]?.blockSize ?? node.offsetHeight); });
|
|
96
|
+
observer.observe(node);
|
|
97
|
+
return () => observer.disconnect();
|
|
98
|
+
}, []);
|
|
99
|
+
return _jsx(motion.span, { className: styles.errorSlot, initial: { height: 0, opacity: 0 }, animate: { height, opacity: 1 }, exit: { height: 0, opacity: 0, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.instant } } }, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.fast } }, children: _jsx("span", { ref: copy, id: id, className: styles.error, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, custom: 1, children: _jsx(motion.span, { className: styles.errorText, custom: 1, variants: reduced ? fade : rise, initial: "enter", animate: "center", exit: "exit", children: text }, text) }) }) });
|
|
100
|
+
}
|
|
101
|
+
export const PasswordStrength = forwardRef(function PasswordStrength({ label, value: valueProp, defaultValue = "", onValueChange, onChange, rules = defaultPasswordRules, error, revealed: revealedProp, onRevealedChange, messages: messagesProp, shortLength, countCharacters, id, className, ...props }, ref) {
|
|
102
|
+
const messages = { ...defaultPasswordStrengthMessages, ...messagesProp, levels: { ...defaultPasswordStrengthMessages.levels, ...messagesProp?.levels } };
|
|
103
|
+
const options = { shortLength, levels: messages.levels, countCharacters };
|
|
104
|
+
const generated = useId();
|
|
105
|
+
const controlId = id ?? generated;
|
|
106
|
+
const rulesId = `${controlId}-rules`, errorId = `${controlId}-error`;
|
|
107
|
+
const hydrated = useHydrated();
|
|
108
|
+
const prefersReduced = useReducedMotion();
|
|
109
|
+
const reduced = hydrated && !!prefersReduced;
|
|
110
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
111
|
+
const value = valueProp ?? internal;
|
|
112
|
+
const [revealedInternal, setRevealedInternal] = useState(false);
|
|
113
|
+
const revealed = revealedProp ?? revealedInternal;
|
|
114
|
+
const strength = estimateStrength(value, rules, options);
|
|
115
|
+
const met = new Set(strength.met);
|
|
116
|
+
// The previous level and rule states tell which way the word moves and which segments and ticks join a staggered wave.
|
|
117
|
+
const rank = strength.short ? .5 : strength.level;
|
|
118
|
+
const metKey = strength.met.join(" ");
|
|
119
|
+
const [track, setTrack] = useState({ rank, level: strength.level, previousLevel: strength.level, direction: 1, metKey, previousMet: metKey });
|
|
120
|
+
if (track.rank !== rank || track.metKey !== metKey)
|
|
121
|
+
setTrack({ rank, level: strength.level, previousLevel: track.rank !== rank ? track.level : track.previousLevel, direction: track.rank === rank ? track.direction : rank > track.rank ? 1 : -1, metKey, previousMet: track.metKey !== metKey ? track.metKey : track.previousMet });
|
|
122
|
+
const previousMet = new Set(track.previousMet.split(" ").filter(Boolean));
|
|
123
|
+
const changed = rules.filter(rule => met.has(rule.id) !== previousMet.has(rule.id)).map(rule => rule.id);
|
|
124
|
+
// Only a change after mount resolves the text, so the field never blurs in on first paint.
|
|
125
|
+
const [reveal, setReveal] = useState({ revealed, changed: false });
|
|
126
|
+
if (reveal.revealed !== revealed)
|
|
127
|
+
setReveal({ revealed, changed: true });
|
|
128
|
+
const shake = useMotionValue(0);
|
|
129
|
+
const shaking = useRef(null);
|
|
130
|
+
const lastError = useRef(error);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const previous = lastError.current;
|
|
133
|
+
lastError.current = error;
|
|
134
|
+
if (!error || error === previous || reduced)
|
|
135
|
+
return;
|
|
136
|
+
// A spring released with sideways velocity rings out on its own, the way a refused field shakes on a phone.
|
|
137
|
+
shaking.current?.stop();
|
|
138
|
+
shaking.current = animate(shake, 0, { type: "spring", velocity: -240, stiffness: 900, damping: 15, restDelta: .1 });
|
|
139
|
+
}, [error, reduced, shake]);
|
|
140
|
+
function toggleReveal() {
|
|
141
|
+
if (revealedProp === undefined)
|
|
142
|
+
setRevealedInternal(!revealed);
|
|
143
|
+
onRevealedChange?.(!revealed);
|
|
144
|
+
}
|
|
145
|
+
const summary = strength.level ? messages.summary({ label: strength.label, met: strength.met.length, total: rules.length }) : "";
|
|
146
|
+
const describedBy = [props["aria-describedby"], error ? errorId : undefined, rulesId].filter(Boolean).join(" ");
|
|
147
|
+
return _jsxs("div", { className: styles.field, "data-level": strength.level, children: [_jsx("label", { className: styles.label, htmlFor: controlId, children: label }), _jsxs(motion.div, { className: styles.shell, style: { x: shake }, children: [_jsx("input", { autoComplete: "new-password", autoCapitalize: "off", autoCorrect: "off", spellCheck: false, ...props, ref: ref, id: controlId, type: revealed ? "text" : "password", value: value, onChange: event => { const next = event.target.value; if (valueProp === undefined)
|
|
148
|
+
setInternal(next); onChange?.(event); onValueChange?.(next, estimateStrength(next, rules, options)); }, "aria-invalid": error ? true : props["aria-invalid"], "aria-describedby": describedBy, "data-reveal": reveal.changed ? (revealed ? "shown" : "hidden") : undefined, className: [styles.input, className].filter(Boolean).join(" ") }), _jsx("button", { type: "button", className: styles.reveal, onClick: toggleReveal, "aria-label": messages.showPassword, "aria-pressed": revealed, "aria-controls": controlId, children: _jsx(EyeMorph, { slashed: revealed, reduced: reduced }) })] }), _jsx(AnimatePresence, { initial: false, children: error ? _jsx(ErrorRow, { id: errorId, text: error, reduced: reduced }, "error") : null }), _jsxs("div", { className: styles.meterRow, children: [_jsx("div", { className: styles.meter, role: "meter", "aria-label": messages.meterLabel, "aria-valuemin": 0, "aria-valuemax": 4, "aria-valuenow": strength.level, "aria-valuetext": strength.level ? strength.label : messages.noPassword, children: [0, 1, 2, 3].map(index => {
|
|
149
|
+
const on = index < strength.level;
|
|
150
|
+
// A pasted password fills left to right; clearing empties right to left.
|
|
151
|
+
const wave = on ? index - track.previousLevel : track.previousLevel - 1 - index;
|
|
152
|
+
return _jsx("span", { className: styles.segment, children: _jsx(motion.span, { className: styles.fill, initial: false, animate: { x: on ? "0%" : "-101%" }, transition: reduced ? { duration: 0 } : { ...motionTokens.spring.smooth, delay: Math.max(0, wave) * (on ? .05 : .03) } }) }, index);
|
|
153
|
+
}) }), _jsx("span", { className: styles.word, "aria-hidden": "true", children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, custom: track.direction, children: strength.level ? _jsx(motion.span, { className: styles.wordText, custom: track.direction, variants: reduced ? fade : rise, initial: "enter", animate: "center", exit: "exit", children: strength.label }, strength.label) : null }) })] }), _jsx("ul", { id: rulesId, className: styles.rules, "aria-label": messages.rulesLabel, children: rules.map(rule => {
|
|
154
|
+
const ok = met.has(rule.id);
|
|
155
|
+
const remaining = value && !ok ? rule.remaining?.(value) ?? 0 : 0;
|
|
156
|
+
const order = changed.indexOf(rule.id);
|
|
157
|
+
return _jsxs("li", { className: styles.rule, "data-met": ok || undefined, children: [_jsx(RuleMark, { met: ok, delay: order > 0 ? order * .05 : 0, reduced: reduced }), _jsxs("span", { className: styles.ruleLabel, children: [rule.label, _jsx("span", { className: styles.srOnly, children: ok ? messages.ruleMet : messages.ruleNotMet })] }), _jsx("span", { className: styles.remaining, "aria-hidden": "true", children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, custom: 1, children: remaining > 0 ? _jsxs(motion.span, { className: styles.remainingText, custom: 1, variants: reduced ? fade : rise, initial: "enter", animate: "center", exit: "exit", children: [messages.remainingPrefix ? `${messages.remainingPrefix} ` : null, _jsx(RollingNumber, { value: remaining, reduced: reduced }), messages.remainingSuffix ? ` ${messages.remainingSuffix}` : null] }, "remaining") : null }) })] }, rule.id);
|
|
158
|
+
}) }), _jsx("span", { className: styles.srOnly, role: "status", children: summary })] });
|
|
159
|
+
});
|
|
160
|
+
PasswordStrength.displayName = "PasswordStrength";
|
|
161
|
+
export default PasswordStrength;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* No row gap: a closing error row would still pay it. Each row carries its own space instead. */
|
|
2
|
+
.field { --tone: var(--border-strong); display: grid; min-width: 0; }
|
|
3
|
+
.field[data-level="1"] { --tone: var(--danger); }
|
|
4
|
+
.field[data-level="2"] { --tone: var(--warning); }
|
|
5
|
+
.field[data-level="3"], .field[data-level="4"] { --tone: var(--success); }
|
|
6
|
+
.label { margin-bottom: var(--space-related); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); }
|
|
7
|
+
/* The shell only transitions color; its sideways shake is a spring on transform, so the two never fight. */
|
|
8
|
+
.shell { display: flex; min-height: var(--control-height-md); align-items: center; gap: var(--space-tight); padding: 0 5px 0 var(--space-inset); border: 1px solid var(--border-strong); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); transition: border-color var(--duration-fast) var(--ease-standard); }
|
|
9
|
+
@media (hover: hover) and (pointer: fine) { .shell:hover:not(:focus-within) { border-color: var(--text-muted); } }
|
|
10
|
+
.shell:focus-within { border-color: var(--foreground); }
|
|
11
|
+
.shell:has(.input[aria-invalid="true"]) { border-color: var(--danger); }
|
|
12
|
+
.input { width: 100%; min-width: 0; height: calc(var(--control-height-md) - 2px); padding: 0; border: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); letter-spacing: var(--tracking-body); outline: 0; }
|
|
13
|
+
.input::placeholder { color: var(--text-muted); }
|
|
14
|
+
/* Touch keyboards zoom into fields under 16px, so coarse pointers get the larger size. */
|
|
15
|
+
@media (pointer: coarse) { .input { font-size: var(--text-base); } }
|
|
16
|
+
/* Dots and characters resolve into each other instead of snapping. Swapping the keyframe name restarts it. */
|
|
17
|
+
.input[data-reveal="shown"] { animation: resolve-shown var(--duration-standard) var(--ease-enter); }
|
|
18
|
+
.input[data-reveal="hidden"] { animation: resolve-hidden var(--duration-standard) var(--ease-enter); }
|
|
19
|
+
@keyframes resolve-shown { from { opacity: .35; filter: blur(2px); } }
|
|
20
|
+
@keyframes resolve-hidden { from { opacity: .35; filter: blur(2px); } }
|
|
21
|
+
.reveal { display: grid; width: 34px; height: 34px; flex: 0 0 auto; place-items: center; padding: 0; border: 0; border-radius: calc(var(--radius-control) - 5px); corner-shape: var(--corner-shape); background: transparent; color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform var(--duration-spring) var(--ease-spring); }
|
|
22
|
+
@media (hover: hover) and (pointer: fine) { .reveal:hover { background: var(--surface-muted); color: var(--foreground); } }
|
|
23
|
+
.reveal:active { transform: scale(.94); transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform 100ms var(--ease-standard); }
|
|
24
|
+
.reveal[aria-pressed="true"] { color: var(--foreground); }
|
|
25
|
+
/* The row's space is padding inside the clipped slot, so its height opens from a true zero. */
|
|
26
|
+
.errorSlot { display: block; overflow: hidden; }
|
|
27
|
+
.error { position: relative; display: block; padding-top: var(--space-related); color: var(--danger); font-size: var(--text-xs); line-height: var(--leading-body); }
|
|
28
|
+
.errorText { display: block; }
|
|
29
|
+
.meterRow { display: flex; align-items: center; gap: var(--space-group); margin-top: var(--space-group); }
|
|
30
|
+
.meter { display: grid; flex: 1 1 auto; min-width: 0; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 5px; }
|
|
31
|
+
/* Each fill slides in from the left inside its clipped track, so the rounded ends keep their shape at every frame. */
|
|
32
|
+
.segment { position: relative; height: 4px; overflow: hidden; isolation: isolate; border-radius: var(--radius-full); corner-shape: round; background: var(--border); }
|
|
33
|
+
.fill { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; background: var(--tone); transition: background-color var(--duration-standard) var(--ease-standard); }
|
|
34
|
+
/* A fixed slot: the meter never resizes when the word changes, and the word stays flush with the field's edge. */
|
|
35
|
+
.word { position: relative; display: block; flex: 0 0 4.5rem; height: 1.4em; color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); text-align: right; white-space: nowrap; }
|
|
36
|
+
.wordText { display: inline-block; }
|
|
37
|
+
.rules { display: grid; gap: 6px; margin: var(--space-inset) 0 0; padding: 0; list-style: none; }
|
|
38
|
+
.rule { display: flex; min-height: 1.25rem; align-items: center; gap: 10px; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--leading-body); transition: color var(--duration-standard) var(--ease-standard); }
|
|
39
|
+
.rule[data-met] { color: var(--foreground); }
|
|
40
|
+
.ruleLabel { min-width: 0; }
|
|
41
|
+
/* The outline hands over to a faint tint while the tick draws on top; most of the color sits on the tick. */
|
|
42
|
+
.mark { position: relative; display: block; width: 16px; height: 16px; flex: 0 0 16px; border: 1.25px solid var(--border-strong); border-radius: var(--radius-full); corner-shape: round; color: var(--success); transition: border-color var(--duration-fast) var(--ease-standard); }
|
|
43
|
+
.rule[data-met] .mark { border-color: transparent; }
|
|
44
|
+
.markFill { position: absolute; inset: -1.25px; border-radius: inherit; corner-shape: inherit; background: color-mix(in oklab, var(--success) 16%, transparent); }
|
|
45
|
+
.tick { position: absolute; inset: -1.25px; width: 16px; height: 16px; }
|
|
46
|
+
.remaining { position: relative; display: block; margin-left: auto; padding-left: var(--space-related); color: var(--text-muted); font-size: var(--text-xs); white-space: nowrap; }
|
|
47
|
+
.remainingText { display: inline-flex; align-items: baseline; gap: .28em; }
|
|
48
|
+
.number { display: inline-flex; font-variant-numeric: tabular-nums; }
|
|
49
|
+
/* Places clip sideways only, so a narrowing column never shows half a digit and a turning digit is never cut. */
|
|
50
|
+
.place { position: relative; display: inline-block; overflow-x: clip; }
|
|
51
|
+
.digit { display: inline-block; }
|
|
52
|
+
.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
53
|
+
@media (prefers-reduced-motion: reduce) {
|
|
54
|
+
.shell, .reveal, .fill, .rule, .mark { transition: none; }
|
|
55
|
+
.reveal:active { transform: none; }
|
|
56
|
+
.input[data-reveal] { animation: none; }
|
|
57
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { FocusEvent as ReactFocusEvent } from "react";
|
|
2
|
+
export interface PhoneCountry {
|
|
3
|
+
/** ISO 3166-1 alpha-2 code, such as "US". */
|
|
4
|
+
iso: string;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Country calling code without the plus, such as "44". */
|
|
7
|
+
dial: string;
|
|
8
|
+
/** National formats from shortest to longest. `#` is a digit, anything else is a separator. */
|
|
9
|
+
patterns: string[];
|
|
10
|
+
/** A national number people may type before the number itself, such as "0" in "07911 123456". */
|
|
11
|
+
trunk: string;
|
|
12
|
+
/** A realistic national number, used for the placeholder and the typing guide. */
|
|
13
|
+
example: string;
|
|
14
|
+
/** Area codes that pick this country when several share a calling code, such as Canada inside +1. */
|
|
15
|
+
areaCodes?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const PHONE_COUNTRIES: PhoneCountry[];
|
|
18
|
+
/** Regional indicator letters. Platforms without flag glyphs show the two letters, which still read. */
|
|
19
|
+
export declare const flagOf: (iso: string) => string;
|
|
20
|
+
/** Formats national digits as typed, keeping a typed trunk prefix: "07911123456" in the UK reads "07911 123456". */
|
|
21
|
+
export declare function formatNational(entry: PhoneCountry, digits: string): string;
|
|
22
|
+
export type PhoneStatus = "empty" | "incomplete" | "valid" | "too-long";
|
|
23
|
+
/** Reads "+44 (0)7911 123456", "0044 7911…", or "+14165550123" into a country and national digits. Longest calling code wins. */
|
|
24
|
+
export declare function parsePhoneNumber(input: string, pool?: PhoneCountry[]): {
|
|
25
|
+
country: PhoneCountry;
|
|
26
|
+
national: string;
|
|
27
|
+
} | null;
|
|
28
|
+
/** Formats an E.164 number for display, such as "+44 7400 123456". Returns the input when it cannot be read. */
|
|
29
|
+
export declare function formatPhoneNumber(e164: string): string;
|
|
30
|
+
export interface PhoneInputDetails {
|
|
31
|
+
country: PhoneCountry;
|
|
32
|
+
/** The number as shown in the field, without the calling code. */
|
|
33
|
+
formatted: string;
|
|
34
|
+
status: PhoneStatus;
|
|
35
|
+
valid: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface PhoneInputProps {
|
|
38
|
+
label: string;
|
|
39
|
+
hideLabel?: boolean;
|
|
40
|
+
/** The number in E.164, such as "+14155550132". An empty string clears the field. */
|
|
41
|
+
value?: string;
|
|
42
|
+
defaultValue?: string;
|
|
43
|
+
/** Fires on every edit with the E.164 number (empty when there are no digits) and its parsed details. */
|
|
44
|
+
onValueChange?: (value: string, details: PhoneInputDetails) => void;
|
|
45
|
+
/** ISO code of the selected country. */
|
|
46
|
+
country?: string;
|
|
47
|
+
/** ISO code used until someone picks a country or enters an international number. */
|
|
48
|
+
defaultCountry?: string;
|
|
49
|
+
onCountryChange?: (iso: string) => void;
|
|
50
|
+
/** Limit the picker to these ISO codes. */
|
|
51
|
+
countries?: string[];
|
|
52
|
+
/** Pinned at the top of the picker under "Suggested". */
|
|
53
|
+
preferredCountries?: string[];
|
|
54
|
+
description?: string;
|
|
55
|
+
/** Replaces the built-in validation message. */
|
|
56
|
+
error?: string;
|
|
57
|
+
/** Show a message after blur when the number is incomplete. On by default. */
|
|
58
|
+
validate?: boolean;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
/** Adds a hidden input carrying the E.164 value for native form submission. */
|
|
62
|
+
name?: string;
|
|
63
|
+
id?: string;
|
|
64
|
+
className?: string;
|
|
65
|
+
onBlur?: (event: ReactFocusEvent<HTMLInputElement>) => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A phone number field with a country picker. The country button grows into a searchable list, the number formats as it is
|
|
69
|
+
* typed with a faint guide showing the rest of the expected shape, pasted or autofilled international numbers pick their own
|
|
70
|
+
* country, and the value comes out in E.164. Typing "+" in the number jumps into the picker with a calling code search.
|
|
71
|
+
*/
|
|
72
|
+
export declare const PhoneInput: import("react").ForwardRefExoticComponent<PhoneInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
73
|
+
export default PhoneInput;
|