@sovereignfs/ui 0.37.0 → 0.40.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/dist/FileDropzone.module.css +63 -0
- package/dist/index.d.ts +70 -3
- package/dist/index.js +522 -370
- package/package.json +1 -1
- package/src/components/FileDropzone/FileDropzone.module.css +63 -0
- package/src/components/FileDropzone/FileDropzone.tsx +109 -0
- package/src/components/FileDropzone/__tests__/FileDropzone.test.tsx +67 -0
- package/src/components/Icon/icons/external-link.tsx +22 -0
- package/src/components/Icon/icons/file.tsx +21 -0
- package/src/components/Icon/icons/index.ts +12 -0
- package/src/components/Icon/icons/upload.tsx +22 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCommitOnEnterOrBlur.ts +39 -0
- package/src/index.ts +10 -2
- package/src/stories/DesignSystemOverview.stories.tsx +27 -1
- package/src/stories/FileDropzone.stories.tsx +69 -0
- package/src/stories/InteractionHooks.stories.tsx +55 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
.dropzone {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: var(--sv-space-4);
|
|
5
|
+
width: 100%;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
padding: var(--sv-space-4) var(--sv-space-5);
|
|
8
|
+
border: 1.5px dashed var(--sv-color-border);
|
|
9
|
+
border-radius: var(--sv-radius-md);
|
|
10
|
+
background-color: var(--sv-color-surface-raised);
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
transition:
|
|
13
|
+
background-color 0.12s ease,
|
|
14
|
+
border-color 0.12s ease;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@media (hover: hover) {
|
|
18
|
+
.dropzone:hover {
|
|
19
|
+
background-color: var(--sv-color-surface-sunken);
|
|
20
|
+
border-color: var(--sv-color-border-strong);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dropzoneActive {
|
|
25
|
+
background-color: var(--sv-color-surface-sunken);
|
|
26
|
+
border-color: var(--sv-color-border-strong);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dropzoneDisabled {
|
|
30
|
+
opacity: 0.5;
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.icon {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
flex-shrink: 0;
|
|
37
|
+
color: var(--sv-color-text-muted);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.text {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 2px;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.title {
|
|
48
|
+
font-size: var(--sv-font-size-sm);
|
|
49
|
+
font-weight: var(--sv-font-weight-medium);
|
|
50
|
+
color: var(--sv-color-text-primary);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.hint {
|
|
57
|
+
font-size: var(--sv-font-size-xs);
|
|
58
|
+
color: var(--sv-color-text-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.hiddenInput {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, CSSProperties, InputHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, PointerEvent, MouseEvent } from 'react';
|
|
2
|
+
import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, CSSProperties, InputHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, PointerEvent, MouseEvent, KeyboardEvent } from 'react';
|
|
3
3
|
|
|
4
4
|
type BadgeVariant = 'role' | 'status' | 'mono';
|
|
5
5
|
type BadgeStatus = 'active' | 'enabled' | 'deactivated' | 'failed' | 'invited' | 'pending' | 'neutral';
|
|
@@ -18,7 +18,7 @@ interface BadgeProps {
|
|
|
18
18
|
*/
|
|
19
19
|
declare function Badge({ variant, status, children }: BadgeProps): react.JSX.Element;
|
|
20
20
|
|
|
21
|
-
type IconName = 'house' | 'settings' | 'log-out' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical' | 'banana' | 'apple' | 'carrot' | 'egg' | 'beef' | 'fish' | 'coffee' | 'wine' | 'beer' | 'cookie' | 'pizza' | 'candy' | 'salad' | 'milk' | 'drumstick' | 'croissant' | 'cup-soda' | 'spray-can' | 'snowflake' | 'shopping-basket';
|
|
21
|
+
type IconName = 'house' | 'settings' | 'log-out' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical' | 'file' | 'upload' | 'external-link' | 'banana' | 'apple' | 'carrot' | 'egg' | 'beef' | 'fish' | 'coffee' | 'wine' | 'beer' | 'cookie' | 'pizza' | 'candy' | 'salad' | 'milk' | 'drumstick' | 'croissant' | 'cup-soda' | 'spray-can' | 'snowflake' | 'shopping-basket';
|
|
22
22
|
|
|
23
23
|
/** Decorative icon — visually meaningful but described by surrounding text. */
|
|
24
24
|
type DecorativeProps = {
|
|
@@ -701,6 +701,45 @@ interface DragHandleRowProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
701
701
|
*/
|
|
702
702
|
declare function DragHandleRow({ children, handleProps, isDragging, className, ...rest }: DragHandleRowProps): react.JSX.Element;
|
|
703
703
|
|
|
704
|
+
interface FileDropzoneProps {
|
|
705
|
+
/** Native `<input type="file">` `name` attribute — set this to submit the
|
|
706
|
+
* file as part of a native `<form action={...}>` (Server Action) request. */
|
|
707
|
+
name?: string;
|
|
708
|
+
/** Native `<input type="file">` `accept` attribute, e.g. `".zip,application/zip"`. */
|
|
709
|
+
accept?: string;
|
|
710
|
+
/** Icon shown beside the label — a file-type glyph. Defaults to a generic file icon. */
|
|
711
|
+
icon?: ReactNode;
|
|
712
|
+
/** Primary label — e.g. "Choose a ZIP file", or the selected file's name. */
|
|
713
|
+
label: string;
|
|
714
|
+
/** Secondary hint — e.g. "or drag and drop here", or the selected file's size. */
|
|
715
|
+
hint?: string;
|
|
716
|
+
/** Called with the picked or dropped file, or `null` if the input is cleared. */
|
|
717
|
+
onFileSelect: (file: File | null) => void;
|
|
718
|
+
/** Accessible name for the control — describe what will be uploaded. */
|
|
719
|
+
ariaLabel: string;
|
|
720
|
+
disabled?: boolean;
|
|
721
|
+
className?: string;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* A styled drag-and-drop file picker: a dashed-border dropzone wrapping a
|
|
725
|
+
* visually-hidden native `<input type="file">`, so the native picker still
|
|
726
|
+
* opens on click and the field participates in normal form semantics. A
|
|
727
|
+
* dropped file is written into the input's own `FileList` (via `DataTransfer`)
|
|
728
|
+
* so `name` + native `<form action>` submission sees it exactly like a
|
|
729
|
+
* click-picked file — not just the `onFileSelect` callback.
|
|
730
|
+
*
|
|
731
|
+
* Caller owns the selected-file state and passes `label`/`hint` accordingly
|
|
732
|
+
* (e.g. swap to the picked file's name/size) — this component is otherwise
|
|
733
|
+
* uncontrolled and stateless beyond its own drag-hover visual.
|
|
734
|
+
*
|
|
735
|
+
* Do not pass `required` through to a wrapping form's validation via this
|
|
736
|
+
* field — `display: none` on the hidden input does not exempt it from HTML5
|
|
737
|
+
* constraint validation, so a `required` hidden input can block submission
|
|
738
|
+
* with no visible field for the user to focus. Validate `onFileSelect`'s
|
|
739
|
+
* result in application code instead.
|
|
740
|
+
*/
|
|
741
|
+
declare function FileDropzone({ name, accept, icon, label, hint, onFileSelect, ariaLabel, disabled, className, }: FileDropzoneProps): react.JSX.Element;
|
|
742
|
+
|
|
704
743
|
interface CalendarProps {
|
|
705
744
|
/** Selected date, or `null` for no selection. */
|
|
706
745
|
value: Date | null;
|
|
@@ -979,4 +1018,32 @@ declare const MOBILE_BREAKPOINT_PX = 768;
|
|
|
979
1018
|
*/
|
|
980
1019
|
declare function useIsMobile(breakpointPx?: number): boolean;
|
|
981
1020
|
|
|
982
|
-
|
|
1021
|
+
interface CommitOnEnterOrBlurHandlers {
|
|
1022
|
+
onKeyDown: (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
1023
|
+
onBlur: () => void;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* useCommitOnEnterOrBlur — spreads onto a quick-entry input/textarea so
|
|
1027
|
+
* Enter and losing focus both commit through the same callback.
|
|
1028
|
+
*
|
|
1029
|
+
* iOS Safari adds its own "Previous / Next / Done" toolbar above the
|
|
1030
|
+
* software keyboard whenever more than one focusable field is nearby (a
|
|
1031
|
+
* Sheet with several fields, a form, etc.) — there is no supported way to
|
|
1032
|
+
* suppress it, since it's WebKit's own field-detection heuristic, not
|
|
1033
|
+
* something the page controls. Tapping that toolbar's Done/checkmark only
|
|
1034
|
+
* ever fires a native `blur`; it is not a form submit and dispatches no
|
|
1035
|
+
* keydown, so a field that commits only on Enter silently discards whatever
|
|
1036
|
+
* was typed the moment a user dismisses the keyboard that way instead of
|
|
1037
|
+
* pressing the on-screen Return key — the two dismissal paths look
|
|
1038
|
+
* identical to the user but produce different outcomes.
|
|
1039
|
+
*
|
|
1040
|
+
* `onCommit` is called unconditionally on both Enter and blur — it owns any
|
|
1041
|
+
* empty-value no-op itself, matching how every call site's own commit
|
|
1042
|
+
* function already guards against an empty value before doing anything.
|
|
1043
|
+
* Callers needing more key handling (e.g. Escape-to-cancel) compose their
|
|
1044
|
+
* own `onKeyDown` around the returned one rather than this hook growing
|
|
1045
|
+
* options for every caller's own key bindings.
|
|
1046
|
+
*/
|
|
1047
|
+
declare function useCommitOnEnterOrBlur(onCommit: () => void): CommitOnEnterOrBlurHandlers;
|
|
1048
|
+
|
|
1049
|
+
export { Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeStatus, type BadgeVariant, type BannerVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarProps, Card, type CardProps, CheckableListRow, type CheckableListRowProps, Checkbox, type CheckboxProps, CodeTextarea, type CodeTextareaProps, type CommitOnEnterOrBlurHandlers, ConfirmDialog, type ConfirmDialogProps, DatePicker, type DatePickerProps, Dialog, type DialogProps, type DialogSize, DragHandleRow, type DragHandleRowProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, FileDropzone, type FileDropzoneProps, FormField, type FormFieldProps, type FormFieldRenderProps, Icon, type IconName, IconPicker, type IconPickerProps, type IconProps, Input, type InputProps, type LongPressHandlers, MOBILE_BREAKPOINT_PX, Menu, type MenuEntry, type MenuItem, type MenuLabel, type MenuProps, type MenuSeparator, type NavTabItem, NavTabs, type NavTabsProps, OverlayHeader, type OverlayHeaderProps, PageHeader, type PageHeaderProps, Popover, type PopoverProps, QuantityStepper, type QuantityStepperProps, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectProps, Sheet, type SheetProps, Spinner, type SpinnerProps, SplitPane, type SplitPaneProps, StatusBadge, type StatusBadgeProps, type StatusBadgeStatus, SuggestionInput, type SuggestionInputProps, type SuggestionOption, SystemBanner, type SystemBannerProps, type TabItem, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastContextValue, type ToastItem, ToastProvider, Toggle, type ToggleProps, Tooltip, type TooltipProps, type UseLongPressOptions, useCommitOnEnterOrBlur, useDoubleTapHandler, useIsMobile, useLongPress, useOverlaySecondRow, useSingleOrDoubleTap, useToast };
|