plass-ui 1.0.0 → 1.0.2
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 +165 -0
- package/dist/components/accordion/PlAccordion.js +1 -131
- package/dist/components/accordion/index.js +1 -1
- package/dist/components/alert/PlAlert.js +1 -109
- package/dist/components/alert/index.js +1 -1
- package/dist/components/animate-appear/PlAnimateAppear.js +1 -71
- package/dist/components/animate-appear/index.js +1 -1
- package/dist/components/animate-blink/PlAnimateBlink.js +1 -47
- package/dist/components/animate-blink/index.js +1 -1
- package/dist/components/animate-fade/PlAnimateFade.js +1 -48
- package/dist/components/animate-fade/index.js +1 -1
- package/dist/components/animate-grow/PlAnimateGrow.js +1 -49
- package/dist/components/animate-grow/index.js +1 -1
- package/dist/components/animate-headline/PlAnimateHeadline.js +1 -120
- package/dist/components/animate-headline/index.js +1 -1
- package/dist/components/animate-lighting/PlAnimateLighting.js +1 -65
- package/dist/components/animate-lighting/index.js +1 -1
- package/dist/components/animate-marquee/PlAnimateMarquee.js +1 -94
- package/dist/components/animate-marquee/index.js +1 -1
- package/dist/components/animate-rotate/PlAnimateRotate.js +1 -50
- package/dist/components/animate-rotate/index.js +1 -1
- package/dist/components/animate-slide/PlAnimateSlide.js +1 -49
- package/dist/components/animate-slide/index.js +1 -1
- package/dist/components/animate-typing/PlAnimateTyping.js +1 -177
- package/dist/components/animate-typing/index.js +1 -1
- package/dist/components/animate-zoom/PlAnimateZoom.js +1 -45
- package/dist/components/animate-zoom/index.js +1 -1
- package/dist/components/aspect-ratio/PlAspectRatio.js +1 -58
- package/dist/components/aspect-ratio/index.js +1 -1
- package/dist/components/avatar/PlAvatar.js +1 -121
- package/dist/components/avatar/index.js +1 -1
- package/dist/components/badge/PlBadge.js +1 -168
- package/dist/components/badge/index.js +1 -1
- package/dist/components/blockquote/PlBlockquote.js +1 -112
- package/dist/components/blockquote/index.js +1 -1
- package/dist/components/bottom-navigation/PlBottomNavigation.js +1 -141
- package/dist/components/bottom-navigation/index.js +1 -1
- package/dist/components/box/PlBox.js +1 -46
- package/dist/components/box/index.js +1 -1
- package/dist/components/breadcrumb/PlBreadcrumb.js +1 -201
- package/dist/components/breadcrumb/index.js +1 -1
- package/dist/components/button/PlButton.js +1 -196
- package/dist/components/button/index.js +1 -1
- package/dist/components/button-group/PlButtonGroup.js +1 -64
- package/dist/components/button-group/index.js +1 -1
- package/dist/components/card/PlCard.js +1 -102
- package/dist/components/card/index.js +1 -1
- package/dist/components/carousel/PlCarousel.js +1 -177
- package/dist/components/carousel/index.js +1 -1
- package/dist/components/chat-bubble/PlChatBubble.js +1 -206
- package/dist/components/chat-bubble/index.js +1 -1
- package/dist/components/checkbox/PlCheckbox.js +1 -100
- package/dist/components/checkbox/index.js +1 -1
- package/dist/components/chip/PlChip.js +1 -144
- package/dist/components/chip/index.js +1 -1
- package/dist/components/collapsible/PlCollapsible.js +1 -65
- package/dist/components/collapsible/index.js +1 -1
- package/dist/components/combobox/PlCombobox.js +1 -199
- package/dist/components/combobox/index.js +1 -1
- package/dist/components/container/PlContainer.js +1 -47
- package/dist/components/container/index.js +1 -1
- package/dist/components/date-picker/PlDatePicker.js +1 -87
- package/dist/components/date-picker/index.js +1 -1
- package/dist/components/date-range-picker/PlDateRangePicker.js +1 -152
- package/dist/components/date-range-picker/index.js +1 -1
- package/dist/components/date-time-picker/PlDateTimePicker.js +1 -106
- package/dist/components/date-time-picker/index.js +1 -1
- package/dist/components/divider/PlDivider.js +1 -107
- package/dist/components/divider/index.js +1 -1
- package/dist/components/drawer/PlDrawer.js +1 -190
- package/dist/components/drawer/index.js +1 -1
- package/dist/components/file-picker/PlFilePicker.js +1 -274
- package/dist/components/file-picker/index.js +1 -1
- package/dist/components/floating-bottom-navigation/PlFloatingBottomNavigation.js +1 -291
- package/dist/components/floating-bottom-navigation/index.js +1 -1
- package/dist/components/grid/PlGrid.js +1 -51
- package/dist/components/grid/PlGridItem.js +1 -34
- package/dist/components/grid/index.js +1 -2
- package/dist/components/highlight/PlHighlight.js +1 -185
- package/dist/components/highlight/index.js +1 -1
- package/dist/components/hot-keys/PlHotKeys.js +1 -294
- package/dist/components/hot-keys/index.js +1 -1
- package/dist/components/icon/PlIcon.js +1 -58
- package/dist/components/icon/index.js +1 -1
- package/dist/components/icon-button/PlIconButton.js +1 -37
- package/dist/components/icon-button/index.js +1 -1
- package/dist/components/list/PlList.js +1 -160
- package/dist/components/list/index.js +1 -1
- package/dist/components/menu/PlMenu.js +1 -209
- package/dist/components/menu/index.js +1 -1
- package/dist/components/modal/PlModal.js +1 -150
- package/dist/components/modal/index.js +1 -1
- package/dist/components/number-field/PlNumberField.js +1 -111
- package/dist/components/number-field/index.js +1 -1
- package/dist/components/otp-field/PlOtpField.js +1 -109
- package/dist/components/otp-field/index.js +1 -1
- package/dist/components/overlay/PlOverlay.js +1 -77
- package/dist/components/overlay/index.js +1 -1
- package/dist/components/pagination/PlPagination.js +1 -159
- package/dist/components/pagination/index.js +1 -1
- package/dist/components/panes/PlPanes.js +1 -324
- package/dist/components/panes/index.js +1 -1
- package/dist/components/pill/PlPill.js +1 -154
- package/dist/components/pill/index.js +1 -1
- package/dist/components/popover/PlPopover.js +1 -113
- package/dist/components/popover/index.js +1 -1
- package/dist/components/progress-box/PlProgressBox.js +1 -34
- package/dist/components/progress-box/index.js +1 -1
- package/dist/components/progress-circular/PlProgressCircular.js +1 -47
- package/dist/components/progress-circular/index.js +1 -1
- package/dist/components/progress-linear/PlProgressLinear.js +1 -40
- package/dist/components/progress-linear/index.js +1 -1
- package/dist/components/radio-group/PlRadioGroup.js +1 -99
- package/dist/components/radio-group/index.js +1 -1
- package/dist/components/rating/PlRating.js +1 -101
- package/dist/components/rating/index.js +1 -1
- package/dist/components/scroll-zone/PlScrollZone.js +1 -411
- package/dist/components/scroll-zone/index.js +1 -1
- package/dist/components/segmented-button/PlSegmentedButton.js +1 -206
- package/dist/components/segmented-button/index.js +1 -1
- package/dist/components/select/PlSelect.js +1 -115
- package/dist/components/select/index.js +1 -1
- package/dist/components/skeleton/PlSkeleton.js +1 -142
- package/dist/components/skeleton/index.js +1 -1
- package/dist/components/slider/PlSlider.js +1 -132
- package/dist/components/slider/index.js +1 -1
- package/dist/components/spoiler/PlSpoiler.js +1 -74
- package/dist/components/spoiler/index.js +1 -1
- package/dist/components/switch/PlSwitch.js +1 -131
- package/dist/components/switch/index.js +1 -1
- package/dist/components/table/PlTable.js +1 -210
- package/dist/components/table/index.js +1 -1
- package/dist/components/tabs/PlTabs.js +1 -182
- package/dist/components/tabs/index.js +1 -1
- package/dist/components/text-field/PlTextField.js +1 -126
- package/dist/components/text-field/index.js +1 -1
- package/dist/components/text-link/PlTextLink.js +1 -136
- package/dist/components/text-link/index.js +1 -1
- package/dist/components/time-picker/PlTimePicker.js +1 -97
- package/dist/components/time-picker/index.js +1 -1
- package/dist/components/timeline/PlTimeline.js +1 -207
- package/dist/components/timeline/index.js +1 -1
- package/dist/components/toast/PlToast.js +1 -174
- package/dist/components/toast/index.js +1 -1
- package/dist/components/toolbar/PlToolbar.js +1 -65
- package/dist/components/toolbar/index.js +1 -1
- package/dist/components/tooltip/PlTooltip.js +1 -117
- package/dist/components/tooltip/index.js +1 -1
- package/dist/components/typography/PlTypography.js +1 -143
- package/dist/components/typography/index.js +1 -1
- package/dist/index.js +1 -75
- package/dist/internal/animate.js +1 -318
- package/dist/internal/button-group.js +2 -19
- package/dist/internal/calendar.js +1 -501
- package/dist/internal/date.js +1 -353
- package/dist/internal/format.js +1 -41
- package/dist/internal/grid.js +1 -143
- package/dist/internal/icons.js +1 -138
- package/dist/internal/menu.js +1 -18
- package/dist/internal/picker.js +1 -139
- package/dist/internal/progress.js +1 -167
- package/dist/internal/sizer.js +1 -35
- package/dist/internal/styles.js +1 -636
- package/dist/types.js +0 -8
- package/package.json +5 -3
|
@@ -1,190 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Dialog as BaseUIDialog } from '@base-ui/react/dialog';
|
|
3
|
-
import { CloseIcon } from '../../internal/icons.js';
|
|
4
|
-
import { cx, focusRingClasses, glassClasses, hasContent, metaTextClasses, sheetBodyClasses, sheetHeaderGapClasses, sheetLineClasses, sheetPaddingXClasses, sheetPaddingYClasses, sheetSectionGapClasses, sheetTitleClasses, surfaceSlots } from '../../internal/styles.js';
|
|
5
|
-
/**
|
|
6
|
-
* How wide a `left` or `right` panel is when nothing says otherwise.
|
|
7
|
-
*
|
|
8
|
-
* Its own ladder rather than `PlModal`'s `maxWidth`, and deliberately narrower
|
|
9
|
-
* at every step: a modal is measured by how long a line of text is comfortable
|
|
10
|
-
* inside it, and a drawer is measured by how much of the window it is willing
|
|
11
|
-
* to take away from what it is a drawer *for*.
|
|
12
|
-
*
|
|
13
|
-
* A `top` or `bottom` panel has no entry here on purpose — its extent is its
|
|
14
|
-
* content, capped at 85% of the window, because a bottom sheet holding three
|
|
15
|
-
* rows should be three rows tall.
|
|
16
|
-
*/
|
|
17
|
-
const extentClasses = {
|
|
18
|
-
xs: 'w-56',
|
|
19
|
-
sm: 'w-64',
|
|
20
|
-
md: 'w-80',
|
|
21
|
-
lg: 'w-96',
|
|
22
|
-
xl: 'w-[28rem]'
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* The corners on the free edge, written out per side and per step because
|
|
26
|
-
* Tailwind only ever sees class names that appear literally in the source.
|
|
27
|
-
*/
|
|
28
|
-
const roundedClasses = {
|
|
29
|
-
left: {
|
|
30
|
-
xs: 'rounded-r-(--plass-radius-xs)',
|
|
31
|
-
sm: 'rounded-r-(--plass-radius-sm)',
|
|
32
|
-
md: 'rounded-r-(--plass-radius-md)',
|
|
33
|
-
lg: 'rounded-r-(--plass-radius-lg)',
|
|
34
|
-
xl: 'rounded-r-(--plass-radius-xl)'
|
|
35
|
-
},
|
|
36
|
-
right: {
|
|
37
|
-
xs: 'rounded-l-(--plass-radius-xs)',
|
|
38
|
-
sm: 'rounded-l-(--plass-radius-sm)',
|
|
39
|
-
md: 'rounded-l-(--plass-radius-md)',
|
|
40
|
-
lg: 'rounded-l-(--plass-radius-lg)',
|
|
41
|
-
xl: 'rounded-l-(--plass-radius-xl)'
|
|
42
|
-
},
|
|
43
|
-
top: {
|
|
44
|
-
xs: 'rounded-b-(--plass-radius-xs)',
|
|
45
|
-
sm: 'rounded-b-(--plass-radius-sm)',
|
|
46
|
-
md: 'rounded-b-(--plass-radius-md)',
|
|
47
|
-
lg: 'rounded-b-(--plass-radius-lg)',
|
|
48
|
-
xl: 'rounded-b-(--plass-radius-xl)'
|
|
49
|
-
},
|
|
50
|
-
bottom: {
|
|
51
|
-
xs: 'rounded-t-(--plass-radius-xs)',
|
|
52
|
-
sm: 'rounded-t-(--plass-radius-sm)',
|
|
53
|
-
md: 'rounded-t-(--plass-radius-md)',
|
|
54
|
-
lg: 'rounded-t-(--plass-radius-lg)',
|
|
55
|
-
xl: 'rounded-t-(--plass-radius-xl)'
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* The hairline on the free edge only. A border all round would draw a line
|
|
60
|
-
* along the window's own edge, where there is nothing on the other side of it
|
|
61
|
-
* to be separated from.
|
|
62
|
-
*/
|
|
63
|
-
const edgeClasses = {
|
|
64
|
-
left: 'border-r',
|
|
65
|
-
right: 'border-l',
|
|
66
|
-
top: 'border-b',
|
|
67
|
-
bottom: 'border-t'
|
|
68
|
-
};
|
|
69
|
-
/** Which end of the viewport the panel is pushed to, and along which axis. */
|
|
70
|
-
const viewportClasses = {
|
|
71
|
-
left: 'flex-row justify-start',
|
|
72
|
-
right: 'flex-row justify-end',
|
|
73
|
-
top: 'flex-col justify-start',
|
|
74
|
-
bottom: 'flex-col justify-end'
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* The sheet.
|
|
78
|
-
*
|
|
79
|
-
* Opacity only, exactly as on `PlModal`. A drawer that slid in would be
|
|
80
|
-
* dragging its own text across the screen for the length of the transition, and
|
|
81
|
-
* a panel is nothing but text and controls — this is the case the no-transform
|
|
82
|
-
* rule was written for, not the exception to it. What says the panel came from
|
|
83
|
-
* an edge is that it is *attached* to one: square against the window, cut on the
|
|
84
|
-
* free side.
|
|
85
|
-
*/
|
|
86
|
-
const panelClasses = /* @__PURE__ */ [
|
|
87
|
-
glassClasses,
|
|
88
|
-
'relative flex flex-col overflow-hidden',
|
|
89
|
-
'text-(--plass-fg) bg-(--plass-glass-press)',
|
|
90
|
-
'[border-color:var(--plass-glass-line)]',
|
|
91
|
-
'[outline:none]'
|
|
92
|
-
].join(' ');
|
|
93
|
-
const overlayShadowClasses = '[box-shadow:var(--plass-shadow-4),var(--plass-gloss-glass)]';
|
|
94
|
-
const inlineShadowClasses = '[box-shadow:var(--plass-gloss-glass)]';
|
|
95
|
-
const fadeClasses = /* @__PURE__ */ [
|
|
96
|
-
'[transition:opacity_var(--plass-duration)_var(--plass-ease)]',
|
|
97
|
-
'data-[starting-style]:opacity-0 data-[ending-style]:opacity-0'
|
|
98
|
-
].join(' ');
|
|
99
|
-
const backdropClasses = /* @__PURE__ */ [
|
|
100
|
-
'fixed inset-0 z-50 bg-(--plass-scrim)',
|
|
101
|
-
'[backdrop-filter:blur(2px)] [-webkit-backdrop-filter:blur(2px)]',
|
|
102
|
-
fadeClasses
|
|
103
|
-
].join(' ');
|
|
104
|
-
/** The × in the corner, shared by both modes. */
|
|
105
|
-
const closeButtonClasses = /* @__PURE__ */ [
|
|
106
|
-
'flex size-[1.6em] shrink-0 cursor-pointer items-center justify-center',
|
|
107
|
-
'rounded-full text-(--plass-muted-fg)',
|
|
108
|
-
'[&_svg]:size-[1.1em] [&_svg]:shrink-0',
|
|
109
|
-
'[transition:background-color_var(--plass-duration)_var(--plass-ease),color_var(--plass-duration)_var(--plass-ease)]',
|
|
110
|
-
'hover:bg-(--p-soft) hover:text-(--plass-fg)',
|
|
111
|
-
focusRingClasses
|
|
112
|
-
].join(' ');
|
|
113
|
-
/**
|
|
114
|
-
* Closes the drawer it is inside.
|
|
115
|
-
*
|
|
116
|
-
* Exported for `PlModalClose`'s reason: an uncontrolled drawer has no `setOpen`
|
|
117
|
-
* for its Cancel button to call. It is an `overlay` drawer's button — an
|
|
118
|
-
* `inline` drawer is not a Base UI dialog and has nothing for this to talk to.
|
|
119
|
-
*
|
|
120
|
-
* `render` is Base UI's own escape hatch, so a real Plass button dismisses:
|
|
121
|
-
* `<PlDrawerClose render={<PlButton variant="ghost">Cancel</PlButton>} />`.
|
|
122
|
-
*/
|
|
123
|
-
export const PlDrawerClose = BaseUIDialog.Close;
|
|
124
|
-
/**
|
|
125
|
-
* A panel attached to one edge of the window.
|
|
126
|
-
*
|
|
127
|
-
* Two things in one component, because they are the same panel: `overlay` is the
|
|
128
|
-
* drawer you open — a scrim, a focus trap, Escape — and `inline` is the drawer
|
|
129
|
-
* that is simply part of the page. Everything else about them is identical,
|
|
130
|
-
* which is exactly why they should not be two components a caller has to switch
|
|
131
|
-
* between when a sidebar becomes a hamburger at a breakpoint.
|
|
132
|
-
*
|
|
133
|
-
* The sections are props rather than compound sub-components, as on `PlCard` and
|
|
134
|
-
* `PlModal`: the arrangement is fixed — heading, description, body, actions —
|
|
135
|
-
* and what a caller wants to decide is what goes in each slot. The body is the
|
|
136
|
-
* only part that scrolls, so the heading and the actions stay put.
|
|
137
|
-
*
|
|
138
|
-
* In `overlay` mode Base UI owns everything hard about it: the focus trap, the
|
|
139
|
-
* scroll lock, the `aria-labelledby` / `aria-describedby` wiring, restoring
|
|
140
|
-
* focus to the trigger, and the inert page behind.
|
|
141
|
-
*/
|
|
142
|
-
export function PlDrawer({ side = 'left', mode = 'overlay', size = 'md', color = 'primary', density = 'default', open, defaultOpen, onOpenChange, trigger, title, description, actions, dividers = false, showClose, closeLabel = 'Close', extent, rounded = true, modal = true, dismissible = true, className, style, children, ...props }) {
|
|
143
|
-
const overlay = mode === 'overlay';
|
|
144
|
-
const along = side === 'left' || side === 'right';
|
|
145
|
-
const showCloseButton = showClose ?? overlay;
|
|
146
|
-
const insetX = sheetPaddingXClasses[density][size];
|
|
147
|
-
const insetY = sheetPaddingYClasses[density][size];
|
|
148
|
-
// With dividers the lines have to reach both edges, so the sheet gives up its
|
|
149
|
-
// padding and every section takes it on instead — the same trade PlCard makes.
|
|
150
|
-
const sectionClasses = dividers ? `${insetX} ${insetY}` : insetX;
|
|
151
|
-
const hasHeader = hasContent(title) || hasContent(description);
|
|
152
|
-
const hasActions = hasContent(actions);
|
|
153
|
-
const sizeStyle = extent === undefined
|
|
154
|
-
? null
|
|
155
|
-
: { [along ? 'width' : 'height']: typeof extent === 'number' ? `${extent}px` : extent };
|
|
156
|
-
const panel = cx(panelClasses, sheetBodyClasses[size], edgeClasses[side], rounded ? roundedClasses[side][size] : '', overlay ? overlayShadowClasses : inlineShadowClasses, overlay ? fadeClasses : '', along
|
|
157
|
-
? `h-full max-w-full ${extent === undefined ? extentClasses[size] : ''}`
|
|
158
|
-
: `w-full ${extent === undefined ? 'max-h-[85%]' : ''}`, dividers ? '' : `${insetY} ${sheetSectionGapClasses[size]}`, className);
|
|
159
|
-
// Base UI's parts carry the `aria-labelledby` / `aria-describedby` wiring an
|
|
160
|
-
// overlay drawer needs. An inline one is not a dialog and needs none, so it
|
|
161
|
-
// gets the plain tags rather than a dialog's parts outside a dialog.
|
|
162
|
-
const TitleTag = overlay ? BaseUIDialog.Title : 'h2';
|
|
163
|
-
const DescriptionTag = overlay ? BaseUIDialog.Description : 'p';
|
|
164
|
-
const contents = (_jsxs(_Fragment, { children: [hasHeader || showCloseButton ? (_jsxs("div", { className: cx('flex shrink-0 items-start gap-3', sectionClasses), children: [_jsxs("div", { className: cx('flex min-w-0 flex-1 flex-col', sheetHeaderGapClasses[size]), children: [hasContent(title) ? (_jsx(TitleTag, { className: cx('m-0 font-semibold', sheetTitleClasses[size]), children: title })) : null, hasContent(description) ? (_jsx(DescriptionTag, { className: cx('m-0 text-(--plass-muted-fg)', metaTextClasses[size]), children: description })) : null] }), showCloseButton ? (overlay ? (_jsx(BaseUIDialog.Close, { "aria-label": closeLabel, className: closeButtonClasses, children: _jsx(CloseIcon, {}) })) : (_jsx("button", { type: "button", "aria-label": closeLabel, className: closeButtonClasses, onClick: () => onOpenChange?.(false), children: _jsx(CloseIcon, {}) }))) : null] })) : null, hasContent(children) ? (_jsx("div", { className: cx('min-h-0 flex-1 overflow-y-auto overscroll-contain', sectionClasses, dividers && (hasHeader || showCloseButton) ? sheetLineClasses : '',
|
|
165
|
-
// A scroll container clips at its padding box and a focus ring is
|
|
166
|
-
// drawn outside the control that owns it, so a field at the top or
|
|
167
|
-
// bottom of an unruled body would have its ring sliced off. The
|
|
168
|
-
// padding is room for the ring and the negative margin hands the
|
|
169
|
-
// space straight back, so nothing on the sheet moves.
|
|
170
|
-
dividers ? '' : '-my-1 py-1'), children: children })) : null, hasActions ? (_jsx("div", { className: cx('flex shrink-0 flex-wrap items-center justify-end gap-2', sectionClasses, dividers ? sheetLineClasses : ''), children: actions })) : null] }));
|
|
171
|
-
if (!overlay) {
|
|
172
|
-
// An inline drawer is in the flow, so "closed" is "not in the layout".
|
|
173
|
-
// There is nothing to animate on the way out: the page around it is what
|
|
174
|
-
// moves, and moving the page is not this component's to do.
|
|
175
|
-
if (!(open ?? defaultOpen ?? true)) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
return (_jsx("div", { className: panel, style: { ...surfaceSlots(color, 0), ...sizeStyle, ...style }, ...props, children: contents }));
|
|
179
|
-
}
|
|
180
|
-
return (_jsxs(BaseUIDialog.Root, { open: open, defaultOpen: defaultOpen, modal: modal, disablePointerDismissal: !dismissible, onOpenChange: (next, details) => {
|
|
181
|
-
// `disablePointerDismissal` covers the click on the scrim; Escape has no
|
|
182
|
-
// prop of its own, so it is cancelled here by the reason it arrives
|
|
183
|
-
// with.
|
|
184
|
-
if (!dismissible && !next && details.reason === 'escape-key') {
|
|
185
|
-
details.cancel();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
onOpenChange?.(next);
|
|
189
|
-
}, children: [trigger ? _jsx(BaseUIDialog.Trigger, { render: trigger }) : null, _jsxs(BaseUIDialog.Portal, { children: [_jsx(BaseUIDialog.Backdrop, { className: `plass-portal ${backdropClasses}` }), _jsx(BaseUIDialog.Viewport, { className: cx('plass-portal fixed inset-0 z-50 flex', viewportClasses[side]), children: _jsx(BaseUIDialog.Popup, { className: panel, style: { ...surfaceSlots(color, 3), ...sizeStyle, ...style }, ...props, children: contents }) })] })] }));
|
|
190
|
-
}
|
|
1
|
+
"use client";import{jsx as s,jsxs as l,Fragment as e}from"react/jsx-runtime";import{Dialog as r}from"@base-ui/react/dialog";import{CloseIcon as a}from"../../internal/icons.js";import{cx as o,focusRingClasses as d,glassClasses as i,hasContent as t,metaTextClasses as n,sheetBodyClasses as u,sheetHeaderGapClasses as p,sheetLineClasses as m,sheetPaddingXClasses as c,sheetPaddingYClasses as x,sheetSectionGapClasses as f,sheetTitleClasses as g,surfaceSlots as h}from"../../internal/styles.js";const b={xs:"w-56",sm:"w-64",md:"w-80",lg:"w-96",xl:"w-[28rem]"},v={left:{xs:"rounded-r-(--plass-radius-xs)",sm:"rounded-r-(--plass-radius-sm)",md:"rounded-r-(--plass-radius-md)",lg:"rounded-r-(--plass-radius-lg)",xl:"rounded-r-(--plass-radius-xl)"},right:{xs:"rounded-l-(--plass-radius-xs)",sm:"rounded-l-(--plass-radius-sm)",md:"rounded-l-(--plass-radius-md)",lg:"rounded-l-(--plass-radius-lg)",xl:"rounded-l-(--plass-radius-xl)"},top:{xs:"rounded-b-(--plass-radius-xs)",sm:"rounded-b-(--plass-radius-sm)",md:"rounded-b-(--plass-radius-md)",lg:"rounded-b-(--plass-radius-lg)",xl:"rounded-b-(--plass-radius-xl)"},bottom:{xs:"rounded-t-(--plass-radius-xs)",sm:"rounded-t-(--plass-radius-sm)",md:"rounded-t-(--plass-radius-md)",lg:"rounded-t-(--plass-radius-lg)",xl:"rounded-t-(--plass-radius-xl)"}},y={left:"border-r",right:"border-l",top:"border-b",bottom:"border-t"},w={left:"flex-row justify-start",right:"flex-row justify-end",top:"flex-col justify-start",bottom:"flex-col justify-end"},j=/* @__PURE__ */[i,"relative flex flex-col overflow-hidden","text-(--plass-fg) bg-(--plass-glass-press)","[border-color:var(--plass-glass-line)]","[outline:none]"].join(" "),N=/* @__PURE__ */["[transition:opacity_var(--plass-duration)_var(--plass-ease)]","data-[starting-style]:opacity-0 data-[ending-style]:opacity-0"].join(" "),k=/* @__PURE__ */["fixed inset-0 z-50 bg-(--plass-scrim)","[backdrop-filter:blur(2px)] [-webkit-backdrop-filter:blur(2px)]",N].join(" "),C=/* @__PURE__ */["flex size-[1.6em] shrink-0 cursor-pointer items-center justify-center","rounded-full text-(--plass-muted-fg)","[&_svg]:size-[1.1em] [&_svg]:shrink-0","[transition:background-color_var(--plass-duration)_var(--plass-ease),color_var(--plass-duration)_var(--plass-ease)]","hover:bg-(--p-soft) hover:text-(--plass-fg)",d].join(" ");export const PlDrawerClose=r.Close;export function PlDrawer({side:d="left",mode:i="overlay",size:_="md",color:$="primary",density:z="default",open:D,defaultOpen:P,onOpenChange:O,trigger:T,title:B,description:F,actions:L,dividers:R=!1,showClose:V,closeLabel:q="Close",extent:A,rounded:E=!0,modal:G=!0,dismissible:H=!0,className:I,style:J,children:K,...M}){const Q="overlay"===i,S="left"===d||"right"===d,U=V??Q,W=c[z][_],X=x[z][_],Y=R?`${W} ${X}`:W,Z=t(B)||t(F),ss=t(L),ls=void 0===A?null:{[S?"width":"height"]:"number"==typeof A?`${A}px`:A},es=o(j,u[_],y[d],E?v[d][_]:"",Q?"[box-shadow:var(--plass-shadow-4),var(--plass-gloss-glass)]":"[box-shadow:var(--plass-gloss-glass)]",Q?N:"",S?`h-full max-w-full ${void 0===A?b[_]:""}`:"w-full "+(void 0===A?"max-h-[85%]":""),R?"":`${X} ${f[_]}`,I),rs=Q?r.Title:"h2",as=Q?r.Description:"p",os=l(e,{children:[Z||U?l("div",{className:o("flex shrink-0 items-start gap-3",Y),children:[l("div",{className:o("flex min-w-0 flex-1 flex-col",p[_]),children:[t(B)?s(rs,{className:o("m-0 font-semibold",g[_]),children:B}):null,t(F)?s(as,{className:o("m-0 text-(--plass-muted-fg)",n[_]),children:F}):null]}),U?Q?s(r.Close,{"aria-label":q,className:C,children:s(a,{})}):s("button",{type:"button","aria-label":q,className:C,onClick:()=>O?.(!1),children:s(a,{})}):null]}):null,t(K)?s("div",{className:o("min-h-0 flex-1 overflow-y-auto overscroll-contain",Y,R&&(Z||U)?m:"",R?"":"-my-1 py-1"),children:K}):null,ss?s("div",{className:o("flex shrink-0 flex-wrap items-center justify-end gap-2",Y,R?m:""),children:L}):null]});return Q?l(r.Root,{open:D,defaultOpen:P,modal:G,disablePointerDismissal:!H,onOpenChange:(s,l)=>{H||s||"escape-key"!==l.reason?O?.(s):l.cancel()},children:[T?s(r.Trigger,{render:T}):null,l(r.Portal,{children:[s(r.Backdrop,{className:`plass-portal ${k}`}),s(r.Viewport,{className:o("plass-portal fixed inset-0 z-50 flex",w[d]),children:s(r.Popup,{className:es,style:{...h($,3),...ls,...J},...M,children:os})})]})]}):D??P??!0?s("div",{className:es,style:{...h($,0),...ls,...J},...M,children:os}):null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlDrawer,PlDrawerClose}from"./PlDrawer.js";
|
|
@@ -1,274 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { CloseIcon } from '../../internal/icons.js';
|
|
4
|
-
import { controlTextLeadingClasses, disabledClasses, focusRingClasses, gapClasses, glassClasses, hasContent, iconClasses, metaTextClasses, radiusClasses, readOnlyFilterClasses, sheetTitleClasses, stackGapClasses, surfaceSlots, transitionClasses } from '../../internal/styles.js';
|
|
5
|
-
/**
|
|
6
|
-
* The box's inner padding. Its own ladder rather than the sheet's, because a
|
|
7
|
-
* dropzone is sized by the gesture it has to catch rather than by what is
|
|
8
|
-
* written in it: a target the height of one line of text is a target you miss.
|
|
9
|
-
*/
|
|
10
|
-
const zonePaddingClasses = {
|
|
11
|
-
default: { xs: 'p-4', sm: 'p-5', md: 'p-6', lg: 'p-8', xl: 'p-10' },
|
|
12
|
-
compact: { xs: 'p-2', sm: 'p-3', md: 'p-4', lg: 'p-5', xl: 'p-6' }
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* The three materials, said the way a *container* says them — the sheet is
|
|
16
|
-
* never dyed. What is dropped on it is other people's content.
|
|
17
|
-
*
|
|
18
|
-
* The dashed edge is the one thing all three share, and the one place the
|
|
19
|
-
* library draws a line that is not solid. It is not decoration: a dashed
|
|
20
|
-
* rectangle is the established sign for "this area accepts a drop", and a
|
|
21
|
-
* dropzone that looks like a PlCard is a PlCard nobody tries to drop on.
|
|
22
|
-
*
|
|
23
|
-
* The edge is neutral at rest and takes the family only once the pointer is on
|
|
24
|
-
* it, which is the same arrangement a `glass` PlButton has: the colour is what
|
|
25
|
-
* happens when you engage with the thing, not what the thing is.
|
|
26
|
-
*/
|
|
27
|
-
const zoneRestClasses = {
|
|
28
|
-
solid: /* @__PURE__ */ [
|
|
29
|
-
glassClasses,
|
|
30
|
-
'border-2 border-dashed text-(--plass-fg) bg-(--plass-glass-press)',
|
|
31
|
-
'[border-color:var(--plass-border)]',
|
|
32
|
-
'[box-shadow:var(--p-elev),var(--plass-gloss-glass)]'
|
|
33
|
-
].join(' '),
|
|
34
|
-
glass: /* @__PURE__ */ [
|
|
35
|
-
glassClasses,
|
|
36
|
-
'border-2 border-dashed text-(--plass-fg) bg-(--plass-glass)',
|
|
37
|
-
'[border-color:var(--plass-border)]',
|
|
38
|
-
'[box-shadow:var(--p-elev),var(--plass-gloss-glass)]'
|
|
39
|
-
].join(' '),
|
|
40
|
-
ghost: 'border-2 border-dashed text-(--plass-fg) bg-transparent [border-color:var(--plass-border)]'
|
|
41
|
-
};
|
|
42
|
-
const zoneHoverClasses = {
|
|
43
|
-
solid: 'hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line-hover)]',
|
|
44
|
-
glass: 'hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line-hover)]',
|
|
45
|
-
ghost: 'hover:bg-(--p-soft) hover:[border-color:var(--p-line-hover)]'
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* While a file is over the box.
|
|
49
|
-
*
|
|
50
|
-
* Colour and edge only, and the same two the hover state already uses, one step
|
|
51
|
-
* further along — a dropzone that grows or lifts under the pointer moves the
|
|
52
|
-
* target while the reader is aiming at it.
|
|
53
|
-
*/
|
|
54
|
-
const zoneOverClasses = 'bg-(--p-soft-hover) [border-color:var(--p-ring)]';
|
|
55
|
-
function UploadIcon() {
|
|
56
|
-
return (_jsxs("svg", { viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M8 10.5V2.75m0 0L5.25 5.5M8 2.75 10.75 5.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M2.75 9.75v1.75a1.75 1.75 0 0 0 1.75 1.75h7a1.75 1.75 0 0 0 1.75-1.75V9.75", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* `1.4 MB`, in the units a person reading a file list expects.
|
|
60
|
-
*
|
|
61
|
-
* Base 1000 rather than 1024, and `MB` rather than `MiB`: it is the number
|
|
62
|
-
* every operating system's file browser shows, and a picker that disagrees with
|
|
63
|
-
* the Finder about how big the file is has picked a fight it cannot win.
|
|
64
|
-
*/
|
|
65
|
-
export function formatFileSize(bytes) {
|
|
66
|
-
if (bytes < 1000) {
|
|
67
|
-
return `${bytes} B`;
|
|
68
|
-
}
|
|
69
|
-
const units = ['kB', 'MB', 'GB', 'TB'];
|
|
70
|
-
let value = bytes / 1000;
|
|
71
|
-
let unit = 0;
|
|
72
|
-
while (value >= 1000 && unit < units.length - 1) {
|
|
73
|
-
value /= 1000;
|
|
74
|
-
unit += 1;
|
|
75
|
-
}
|
|
76
|
-
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Whether a file matches an `accept` string.
|
|
80
|
-
*
|
|
81
|
-
* The browser applies `accept` to its own picker and to nothing else, so a file
|
|
82
|
-
* that arrives by drag has never been checked against it. This is that check:
|
|
83
|
-
* the same three forms the attribute takes — `.ext`, `type/subtype`, `type/*`.
|
|
84
|
-
*/
|
|
85
|
-
function matchesAccept(file, accept) {
|
|
86
|
-
const name = file.name.toLowerCase();
|
|
87
|
-
const type = file.type.toLowerCase();
|
|
88
|
-
return accept
|
|
89
|
-
.split(',')
|
|
90
|
-
.map((entry) => entry.trim().toLowerCase())
|
|
91
|
-
.filter(Boolean)
|
|
92
|
-
.some((entry) => {
|
|
93
|
-
if (entry.startsWith('.')) {
|
|
94
|
-
return name.endsWith(entry);
|
|
95
|
-
}
|
|
96
|
-
if (entry.endsWith('/*')) {
|
|
97
|
-
return type.startsWith(entry.slice(0, -1));
|
|
98
|
-
}
|
|
99
|
-
return type === entry;
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* A box you drop files on, or click to open the file dialog.
|
|
104
|
-
*
|
|
105
|
-
* There is no Base UI primitive under this, and the fallback the library allows
|
|
106
|
-
* — plain React and DOM — is the right one: a dropzone is a `<div>` listening
|
|
107
|
-
* for four drag events and an `<input type="file">` it clicks for you. There is
|
|
108
|
-
* no popup to position, no focus to trap and no roving anything.
|
|
109
|
-
*
|
|
110
|
-
* What that leaves is the part every hand-rolled dropzone gets wrong. The drag
|
|
111
|
-
* counter is one: `dragenter` and `dragleave` both fire as the pointer crosses
|
|
112
|
-
* a *child* of the zone, so a zone that toggles a boolean flickers the whole
|
|
113
|
-
* time a file is dragged across its own contents. Counting them is the fix. The
|
|
114
|
-
* other is `accept`, which the browser enforces on its own dialog and never on
|
|
115
|
-
* a drop, so a dropzone that only sets the attribute accepts anything the
|
|
116
|
-
* moment a file arrives by drag.
|
|
117
|
-
*
|
|
118
|
-
* The shell is a `<div>` and the pressable area inside it is a real `<button>`,
|
|
119
|
-
* with the file list outside that button — the remove buttons cannot be nested
|
|
120
|
-
* inside the browse button.
|
|
121
|
-
*/
|
|
122
|
-
export const PlFilePicker = /* @__PURE__ */ React.forwardRef(function PlFilePicker({ variant = 'glass', size = 'md', color = 'primary', density = 'default', elevation = 0, accept, multiple = false, maxSize, maxFiles, value, defaultValue, onFilesChange, onReject, label, description, error, invalid, title, hint, icon, showList = true, removeLabel = (name) => `Remove ${name}`, fullWidth = true, disabled = false, readOnly = false, required = false, name, id, className, style, ...props }, ref) {
|
|
123
|
-
const inputRef = React.useRef(null);
|
|
124
|
-
React.useImperativeHandle(ref, () => inputRef.current);
|
|
125
|
-
const [uncontrolled, setUncontrolled] = React.useState(() => [...(defaultValue ?? [])]);
|
|
126
|
-
// The copy is what stops a caller's array being the one held here, and it
|
|
127
|
-
// has to be memoised: the handlers below close over this list, and a fresh
|
|
128
|
-
// array every render would rebuild all of them every render.
|
|
129
|
-
const files = React.useMemo(() => (value ? [...value] : uncontrolled), [value, uncontrolled]);
|
|
130
|
-
// `dragenter`/`dragleave` fire for every child the pointer crosses, so a
|
|
131
|
-
// boolean flickers the whole time a file is over the box. The depth counter
|
|
132
|
-
// is the only thing that survives a zone with content in it.
|
|
133
|
-
const dragDepth = React.useRef(0);
|
|
134
|
-
const [over, setOver] = React.useState(false);
|
|
135
|
-
const hasError = hasContent(error);
|
|
136
|
-
const isInvalid = invalid ?? hasError;
|
|
137
|
-
// Invalid re-points the whole slot family at `danger`, so the edge, the ring
|
|
138
|
-
// and the message all turn over together — the same wiring PlTextField uses.
|
|
139
|
-
const family = isInvalid ? 'danger' : color;
|
|
140
|
-
const inert = disabled || readOnly;
|
|
141
|
-
const descriptionId = React.useId();
|
|
142
|
-
const commit = React.useCallback((next) => {
|
|
143
|
-
if (!value) {
|
|
144
|
-
setUncontrolled(next);
|
|
145
|
-
}
|
|
146
|
-
onFilesChange?.(next);
|
|
147
|
-
}, [onFilesChange, value]);
|
|
148
|
-
/**
|
|
149
|
-
* Sorts an incoming batch into kept and rejected.
|
|
150
|
-
*
|
|
151
|
-
* The count is checked against what is *already* held rather than against
|
|
152
|
-
* the batch, which is the difference between "you may drop five files" and
|
|
153
|
-
* "you may end up with five files" — only the second is what `maxFiles`
|
|
154
|
-
* means.
|
|
155
|
-
*/
|
|
156
|
-
const accepting = React.useCallback((incoming) => {
|
|
157
|
-
const kept = [];
|
|
158
|
-
const rejections = [];
|
|
159
|
-
const room = multiple ? (maxFiles ?? Number.POSITIVE_INFINITY) : 1;
|
|
160
|
-
const held = multiple ? files.length : 0;
|
|
161
|
-
for (const file of incoming) {
|
|
162
|
-
if (accept && !matchesAccept(file, accept)) {
|
|
163
|
-
rejections.push({ file, reason: 'type' });
|
|
164
|
-
}
|
|
165
|
-
else if (maxSize !== undefined && file.size > maxSize) {
|
|
166
|
-
rejections.push({ file, reason: 'size' });
|
|
167
|
-
}
|
|
168
|
-
else if (held + kept.length >= room) {
|
|
169
|
-
rejections.push({ file, reason: 'count' });
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
kept.push(file);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return { kept, rejections };
|
|
176
|
-
}, [accept, files.length, maxFiles, maxSize, multiple]);
|
|
177
|
-
const add = React.useCallback((incoming) => {
|
|
178
|
-
const { kept, rejections } = accepting(incoming);
|
|
179
|
-
if (rejections.length > 0) {
|
|
180
|
-
onReject?.(rejections);
|
|
181
|
-
}
|
|
182
|
-
if (kept.length > 0) {
|
|
183
|
-
commit(multiple ? [...files, ...kept] : kept);
|
|
184
|
-
}
|
|
185
|
-
}, [accepting, commit, files, multiple, onReject]);
|
|
186
|
-
const browse = () => {
|
|
187
|
-
if (inert) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// Cleared first, so choosing the same file twice in a row still fires
|
|
191
|
-
// `change` — the input holds its value otherwise and the second pick is
|
|
192
|
-
// silently dropped.
|
|
193
|
-
if (inputRef.current) {
|
|
194
|
-
inputRef.current.value = '';
|
|
195
|
-
inputRef.current.click();
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
const zoneClassNames = [
|
|
199
|
-
'flex w-full flex-col items-center justify-center text-center',
|
|
200
|
-
'cursor-pointer select-none',
|
|
201
|
-
zonePaddingClasses[density][size],
|
|
202
|
-
radiusClasses[size],
|
|
203
|
-
gapClasses[size],
|
|
204
|
-
transitionClasses,
|
|
205
|
-
iconClasses,
|
|
206
|
-
focusRingClasses,
|
|
207
|
-
// An if/else rather than stacked variants: two Tailwind classes of equal
|
|
208
|
-
// specificity resolve by their order in the generated stylesheet.
|
|
209
|
-
disabled
|
|
210
|
-
? `${disabledClasses[variant]} border-2 border-dashed`
|
|
211
|
-
: readOnly
|
|
212
|
-
? `${zoneRestClasses[variant]} ${readOnlyFilterClasses} cursor-default`
|
|
213
|
-
: zoneRestClasses[variant],
|
|
214
|
-
!inert ? zoneHoverClasses[variant] : '',
|
|
215
|
-
over && !inert ? zoneOverClasses : ''
|
|
216
|
-
]
|
|
217
|
-
.filter(Boolean)
|
|
218
|
-
.join(' ');
|
|
219
|
-
return (_jsxs("div", { className: [
|
|
220
|
-
'flex-col align-top',
|
|
221
|
-
stackGapClasses[size],
|
|
222
|
-
fullWidth ? 'flex w-full' : 'inline-flex',
|
|
223
|
-
className ?? ''
|
|
224
|
-
]
|
|
225
|
-
.filter(Boolean)
|
|
226
|
-
.join(' '), style: { ...surfaceSlots(family, elevation), ...style }, ...props, children: [hasContent(label) ? (_jsx("span", { className: [
|
|
227
|
-
metaTextClasses[size],
|
|
228
|
-
'font-semibold',
|
|
229
|
-
disabled ? 'text-(--plass-muted-fg)' : 'text-(--plass-fg)'
|
|
230
|
-
].join(' '), children: label })) : null, _jsxs("div", { className: "flex w-full flex-col", onDragEnter: (event) => {
|
|
231
|
-
if (inert) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
event.preventDefault();
|
|
235
|
-
dragDepth.current += 1;
|
|
236
|
-
setOver(true);
|
|
237
|
-
}, onDragOver: (event) => {
|
|
238
|
-
if (inert) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
// Without this the browser navigates to the file instead of
|
|
242
|
-
// dropping it, which is the default and is never what anybody wants.
|
|
243
|
-
event.preventDefault();
|
|
244
|
-
event.dataTransfer.dropEffect = 'copy';
|
|
245
|
-
}, onDragLeave: () => {
|
|
246
|
-
if (inert) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
250
|
-
if (dragDepth.current === 0) {
|
|
251
|
-
setOver(false);
|
|
252
|
-
}
|
|
253
|
-
}, onDrop: (event) => {
|
|
254
|
-
if (inert) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
event.preventDefault();
|
|
258
|
-
dragDepth.current = 0;
|
|
259
|
-
setOver(false);
|
|
260
|
-
add(Array.from(event.dataTransfer.files));
|
|
261
|
-
}, children: [_jsxs("button", { type: "button", id: id, disabled: disabled, "aria-describedby": hasContent(description) || hasError ? descriptionId : undefined, "aria-invalid": isInvalid || undefined, className: zoneClassNames, onClick: browse, children: [icon === undefined ? (_jsx("span", { className: "flex items-center text-(--p-accent) [&_svg]:size-[1.8em]", children: _jsx(UploadIcon, {}) })) : hasContent(icon) ? (_jsx("span", { className: "flex items-center text-(--p-accent) [&_svg]:size-[1.8em]", children: icon })) : null, _jsx("span", { className: `font-semibold ${sheetTitleClasses[size]}`, children: title ?? 'Drop files here, or click to browse' }), hasContent(hint) ? (_jsx("span", { className: `text-(--plass-muted-fg) ${metaTextClasses[size]}`, children: hint })) : null] }), _jsx("input", { ref: inputRef, type: "file", name: name, accept: accept, multiple: multiple, required: required && files.length === 0, disabled: inert, tabIndex: -1, "aria-hidden": "true", className: "absolute size-px overflow-hidden opacity-0 [clip-path:inset(50%)]", onChange: (event) => add(Array.from(event.target.files ?? [])) })] }), showList && files.length > 0 ? (_jsx("ul", { role: "list", className: `flex w-full flex-col ${stackGapClasses[size]} m-0 list-none p-0`, children: files.map((file, index) => (_jsxs("li", { className: [
|
|
262
|
-
'flex w-full items-center gap-2 px-2 py-1.5',
|
|
263
|
-
radiusClasses.xs,
|
|
264
|
-
controlTextLeadingClasses[size],
|
|
265
|
-
'bg-(--p-soft) text-(--plass-fg)'
|
|
266
|
-
].join(' '), children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: file.name }), _jsx("span", { className: `shrink-0 text-(--plass-muted-fg) tabular-nums ${metaTextClasses[size]}`, children: formatFileSize(file.size) }), inert ? null : (_jsx("button", { type: "button", "aria-label": removeLabel(file.name), className: [
|
|
267
|
-
'inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full',
|
|
268
|
-
'size-[1.3em] text-(--plass-muted-fg) opacity-70',
|
|
269
|
-
'[transition:opacity_var(--plass-duration)_var(--plass-ease),color_var(--plass-duration)_var(--plass-ease)]',
|
|
270
|
-
'[&_svg]:size-[0.9em]',
|
|
271
|
-
'hover:text-(--plass-fg) hover:opacity-100 focus-visible:opacity-100',
|
|
272
|
-
focusRingClasses
|
|
273
|
-
].join(' '), onClick: () => commit(files.filter((_, at) => at !== index)), children: _jsx(CloseIcon, {}) }))] }, `${file.name}-${file.size}-${file.lastModified}-${index}`))) })) : null, hasContent(description) && !hasError ? (_jsx("span", { id: descriptionId, className: `${metaTextClasses[size]} text-(--plass-muted-fg)`, children: description })) : null, hasError ? (_jsx("span", { id: descriptionId, className: `${metaTextClasses[size]} text-(--p-accent)`, children: error })) : null] }));
|
|
274
|
-
});
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import*as s from"react";import{CloseIcon as l}from"../../internal/icons.js";import{controlTextLeadingClasses as t,disabledClasses as a,focusRingClasses as o,gapClasses as n,glassClasses as i,hasContent as c,iconClasses as d,metaTextClasses as p,radiusClasses as u,readOnlyFilterClasses as f,sheetTitleClasses as m,stackGapClasses as h,surfaceSlots as v,transitionClasses as g}from"../../internal/styles.js";const b={default:{xs:"p-4",sm:"p-5",md:"p-6",lg:"p-8",xl:"p-10"},compact:{xs:"p-2",sm:"p-3",md:"p-4",lg:"p-5",xl:"p-6"}},x={solid:/* @__PURE__ */[i,"border-2 border-dashed text-(--plass-fg) bg-(--plass-glass-press)","[border-color:var(--plass-border)]","[box-shadow:var(--p-elev),var(--plass-gloss-glass)]"].join(" "),glass:/* @__PURE__ */[i,"border-2 border-dashed text-(--plass-fg) bg-(--plass-glass)","[border-color:var(--plass-border)]","[box-shadow:var(--p-elev),var(--plass-gloss-glass)]"].join(" "),ghost:"border-2 border-dashed text-(--plass-fg) bg-transparent [border-color:var(--plass-border)]"},y={solid:"hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line-hover)]",glass:"hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line-hover)]",ghost:"hover:bg-(--p-soft) hover:[border-color:var(--p-line-hover)]"};function UploadIcon(){return r("svg",{viewBox:"0 0 16 16",fill:"none","aria-hidden":"true",children:[e("path",{d:"M8 10.5V2.75m0 0L5.25 5.5M8 2.75 10.75 5.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"}),e("path",{d:"M2.75 9.75v1.75a1.75 1.75 0 0 0 1.75 1.75h7a1.75 1.75 0 0 0 1.75-1.75V9.75",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]})}export function formatFileSize(e){if(e<1e3)return`${e} B`;const r=["kB","MB","GB","TB"];let s=e/1e3,l=0;for(;s>=1e3&&l<r.length-1;)s/=1e3,l+=1;return`${s<10?s.toFixed(1):Math.round(s)} ${r[l]}`}function matchesAccept(e,r){const s=e.name.toLowerCase(),l=e.type.toLowerCase();return r.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean).some(e=>e.startsWith(".")?s.endsWith(e):e.endsWith("/*")?l.startsWith(e.slice(0,-1)):l===e)}export const PlFilePicker=/* @__PURE__ */s.forwardRef(function PlFilePicker({variant:i="glass",size:k="md",color:N="primary",density:j="default",elevation:$=0,accept:w,multiple:z=!1,maxSize:C,maxFiles:I,value:L,defaultValue:B,onFilesChange:D,onReject:F,label:M,description:_,error:W,invalid:S,title:P,hint:R,icon:T,showList:A=!0,removeLabel:V=e=>`Remove ${e}`,fullWidth:E=!0,disabled:O=!1,readOnly:q=!1,required:U=!1,name:G,id:H,className:Y,style:J,...K},Q){const X=s.useRef(null);s.useImperativeHandle(Q,()=>X.current);const[Z,ee]=s.useState(()=>[...B??[]]),re=s.useMemo(()=>L?[...L]:Z,[L,Z]),se=s.useRef(0),[le,te]=s.useState(!1),ae=c(W),oe=S??ae,ne=oe?"danger":N,ie=O||q,ce=s.useId(),de=s.useCallback(e=>{L||ee(e),D?.(e)},[D,L]),pe=s.useCallback(e=>{const r=[],s=[],l=z?I??Number.POSITIVE_INFINITY:1,t=z?re.length:0;for(const a of e)w&&!matchesAccept(a,w)?s.push({file:a,reason:"type"}):void 0!==C&&a.size>C?s.push({file:a,reason:"size"}):t+r.length>=l?s.push({file:a,reason:"count"}):r.push(a);return{kept:r,rejections:s}},[w,re.length,I,C,z]),ue=s.useCallback(e=>{const{kept:r,rejections:s}=pe(e);s.length>0&&F?.(s),r.length>0&&de(z?[...re,...r]:r)},[pe,de,re,z,F]),fe=["flex w-full flex-col items-center justify-center text-center","cursor-pointer select-none",b[j][k],u[k],n[k],g,d,o,O?`${a[i]} border-2 border-dashed`:q?`${x[i]} ${f} cursor-default`:x[i],ie?"":y[i],le&&!ie?"bg-(--p-soft-hover) [border-color:var(--p-ring)]":""].filter(Boolean).join(" ");return r("div",{className:["flex-col align-top",h[k],E?"flex w-full":"inline-flex",Y??""].filter(Boolean).join(" "),style:{...v(ne,$),...J},...K,children:[c(M)?e("span",{className:[p[k],"font-semibold",O?"text-(--plass-muted-fg)":"text-(--plass-fg)"].join(" "),children:M}):null,r("div",{className:"flex w-full flex-col",onDragEnter:e=>{ie||(e.preventDefault(),se.current+=1,te(!0))},onDragOver:e=>{ie||(e.preventDefault(),e.dataTransfer.dropEffect="copy")},onDragLeave:()=>{ie||(se.current=Math.max(0,se.current-1),0===se.current&&te(!1))},onDrop:e=>{ie||(e.preventDefault(),se.current=0,te(!1),ue(Array.from(e.dataTransfer.files)))},children:[r("button",{type:"button",id:H,disabled:O,"aria-describedby":c(_)||ae?ce:void 0,"aria-invalid":oe||void 0,className:fe,onClick:()=>{ie||X.current&&(X.current.value="",X.current.click())},children:[void 0===T?e("span",{className:"flex items-center text-(--p-accent) [&_svg]:size-[1.8em]",children:e(UploadIcon,{})}):c(T)?e("span",{className:"flex items-center text-(--p-accent) [&_svg]:size-[1.8em]",children:T}):null,e("span",{className:`font-semibold ${m[k]}`,children:P??"Drop files here, or click to browse"}),c(R)?e("span",{className:`text-(--plass-muted-fg) ${p[k]}`,children:R}):null]}),e("input",{ref:X,type:"file",name:G,accept:w,multiple:z,required:U&&0===re.length,disabled:ie,tabIndex:-1,"aria-hidden":"true",className:"absolute size-px overflow-hidden opacity-0 [clip-path:inset(50%)]",onChange:e=>ue(Array.from(e.target.files??[]))})]}),A&&re.length>0?e("ul",{role:"list",className:`flex w-full flex-col ${h[k]} m-0 list-none p-0`,children:re.map((s,a)=>r("li",{className:["flex w-full items-center gap-2 px-2 py-1.5",u.xs,t[k],"bg-(--p-soft) text-(--plass-fg)"].join(" "),children:[e("span",{className:"min-w-0 flex-1 truncate",children:s.name}),e("span",{className:`shrink-0 text-(--plass-muted-fg) tabular-nums ${p[k]}`,children:formatFileSize(s.size)}),ie?null:e("button",{type:"button","aria-label":V(s.name),className:["inline-flex shrink-0 cursor-pointer items-center justify-center rounded-full","size-[1.3em] text-(--plass-muted-fg) opacity-70","[transition:opacity_var(--plass-duration)_var(--plass-ease),color_var(--plass-duration)_var(--plass-ease)]","[&_svg]:size-[0.9em]","hover:text-(--plass-fg) hover:opacity-100 focus-visible:opacity-100",o].join(" "),onClick:()=>de(re.filter((e,r)=>r!==a)),children:e(l,{})})]},`${s.name}-${s.size}-${s.lastModified}-${a}`))}):null,c(_)&&!ae?e("span",{id:ce,className:`${p[k]} text-(--plass-muted-fg)`,children:_}):null,ae?e("span",{id:ce,className:`${p[k]} text-(--p-accent)`,children:W}):null]})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlFilePicker,formatFileSize}from"./PlFilePicker.js";
|