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,141 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { useRender } from '@base-ui/react/use-render';
|
|
4
|
-
import { cx, focusRingInsetClasses, hasContent, iconSizeClasses, metaTextClasses, radiusClasses, sheetLineClasses, sheetRestClasses, srOnlyClasses, surfaceSlots, transitionClasses } from '../../internal/styles.js';
|
|
5
|
-
const BottomNavigationContext = /* @__PURE__ */ React.createContext({
|
|
6
|
-
value: null,
|
|
7
|
-
change: () => { },
|
|
8
|
-
size: 'md',
|
|
9
|
-
density: 'default',
|
|
10
|
-
labels: 'all',
|
|
11
|
-
disabled: false
|
|
12
|
-
});
|
|
13
|
-
const positionClasses = {
|
|
14
|
-
static: '',
|
|
15
|
-
sticky: 'sticky bottom-0 z-20',
|
|
16
|
-
fixed: 'fixed inset-x-0 bottom-0 z-30'
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* The row's floor.
|
|
20
|
-
*
|
|
21
|
-
* `md` is 56px, which is the height a bottom navigation has had since the first
|
|
22
|
-
* one — tall enough for a glyph with a word under it, short enough that it is
|
|
23
|
-
* not competing with the page it is on. The ladder around it keeps the same
|
|
24
|
-
* proportion to the glyph and the name it holds.
|
|
25
|
-
*/
|
|
26
|
-
const rowMinHeightClasses = {
|
|
27
|
-
xs: 'min-h-10',
|
|
28
|
-
sm: 'min-h-12',
|
|
29
|
-
md: 'min-h-14',
|
|
30
|
-
lg: 'min-h-16',
|
|
31
|
-
xl: 'min-h-18'
|
|
32
|
-
};
|
|
33
|
-
/** The air inside the sheet, around the row of items. */
|
|
34
|
-
const rowPaddingClasses = {
|
|
35
|
-
default: { xs: 'p-1', sm: 'p-1', md: 'p-1.5', lg: 'p-2', xl: 'p-2.5' },
|
|
36
|
-
compact: { xs: 'p-0.5', sm: 'p-0.5', md: 'p-1', lg: 'p-1', xl: 'p-1.5' }
|
|
37
|
-
};
|
|
38
|
-
/** Between the glyph and the name under it. */
|
|
39
|
-
const itemGapClasses = {
|
|
40
|
-
xs: 'gap-0.5',
|
|
41
|
-
sm: 'gap-0.5',
|
|
42
|
-
md: 'gap-1',
|
|
43
|
-
lg: 'gap-1',
|
|
44
|
-
xl: 'gap-1.5'
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* A row of destinations held against the bottom edge of the window.
|
|
48
|
-
*
|
|
49
|
-
* It is a `<nav>` rather than a `role="tablist"`, and its items are ordinary
|
|
50
|
-
* links or buttons rather than tabs. That is a deliberate choice about what is
|
|
51
|
-
* being promised: a tab list owes a keyboard reader one tab stop for the whole
|
|
52
|
-
* set and arrow keys within it, and it owes a screen reader a panel per tab. A
|
|
53
|
-
* bottom navigation switches what the *page* is, not which panel of one is
|
|
54
|
-
* showing, and claiming the role without the behaviour is worse for a keyboard
|
|
55
|
-
* reader than never claiming it. What is claimed instead is `aria-current`,
|
|
56
|
-
* which is the honest statement: this is the destination you are on.
|
|
57
|
-
*
|
|
58
|
-
* The sheet is never dyed, exactly as on a `PlCard`. What carries the colour
|
|
59
|
-
* family is the one item that is current.
|
|
60
|
-
*/
|
|
61
|
-
export const PlBottomNavigation = /* @__PURE__ */ React.forwardRef(function PlBottomNavigation({ variant = 'glass', size = 'md', color = 'primary', density = 'default', elevation = 0, value: valueProp, defaultValue = null, onValueChange, position = 'fixed', labels = 'all', divider = true, safeArea = true, disabled = false, label, render, className, style, children, ...props }, ref) {
|
|
62
|
-
const [uncontrolled, setUncontrolled] = React.useState(defaultValue);
|
|
63
|
-
const controlled = valueProp !== undefined;
|
|
64
|
-
const value = controlled ? valueProp : uncontrolled;
|
|
65
|
-
const change = React.useCallback((next) => {
|
|
66
|
-
if (!controlled) {
|
|
67
|
-
setUncontrolled(next);
|
|
68
|
-
}
|
|
69
|
-
onValueChange?.(next);
|
|
70
|
-
}, [controlled, onValueChange]);
|
|
71
|
-
const context = React.useMemo(() => ({ value: value ?? null, change, size, density, labels, disabled }), [value, change, size, density, labels, disabled]);
|
|
72
|
-
const classNames = cx('w-full min-w-0',
|
|
73
|
-
// A bar spanning an edge of the window has nothing behind its corners, so
|
|
74
|
-
// only one sitting in the flow is a sheet with corners at all.
|
|
75
|
-
position === 'static' ? radiusClasses[size] : '', sheetRestClasses[variant],
|
|
76
|
-
// A bar that is not in the flow has three edges against the window and one
|
|
77
|
-
// against the content, so only that one is ever drawn — a hairline down
|
|
78
|
-
// the left of the screen is a hairline nobody asked for. In the flow it is
|
|
79
|
-
// an ordinary sheet and keeps the whole outline `sheetRestClasses` gave it.
|
|
80
|
-
variant === 'glass' && position !== 'static' ? 'border-0' : '', divider ? sheetLineClasses : '', positionClasses[position],
|
|
81
|
-
// The sheet keeps reaching the bottom of the screen; what the inset moves
|
|
82
|
-
// is the row inside it. A bar that stopped above the home indicator would
|
|
83
|
-
// leave a stripe of page showing under the glass.
|
|
84
|
-
safeArea ? 'pb-[env(safe-area-inset-bottom)]' : '', transitionClasses, className);
|
|
85
|
-
return useRender({
|
|
86
|
-
render: render ?? _jsx("nav", {}),
|
|
87
|
-
ref,
|
|
88
|
-
props: {
|
|
89
|
-
'aria-label': label,
|
|
90
|
-
className: classNames,
|
|
91
|
-
style: { ...surfaceSlots(color, elevation), ...style },
|
|
92
|
-
children: (_jsx(BottomNavigationContext.Provider, { value: context, children: _jsx("div", { className: cx('flex w-full items-stretch', rowMinHeightClasses[size], rowPaddingClasses[density][size]), children: children }) })),
|
|
93
|
-
...props
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
/**
|
|
98
|
-
* One destination.
|
|
99
|
-
*
|
|
100
|
-
* It has no `size`, no `color` and no `variant` of its own: all three belong to
|
|
101
|
-
* the bar, which is the only place they can be set once and mean the same thing
|
|
102
|
-
* for every item.
|
|
103
|
-
*
|
|
104
|
-
* With an `href` it is a real `<a>`, which is what makes a long press offer
|
|
105
|
-
* "open in a new tab" and what puts the destination in the status bar — neither
|
|
106
|
-
* of which a `<button>` that calls `router.push` can do. Without one it is a
|
|
107
|
-
* `<button>`, because a `<div>` carrying a click handler is invisible to a
|
|
108
|
-
* keyboard.
|
|
109
|
-
*/
|
|
110
|
-
export const PlBottomNavigationItem = /* @__PURE__ */ React.forwardRef(function PlBottomNavigationItem({ value, icon, href, disabled: disabledProp = false, className, children, onClick, ...props }, ref) {
|
|
111
|
-
const bar = React.useContext(BottomNavigationContext);
|
|
112
|
-
const disabled = disabledProp || bar.disabled;
|
|
113
|
-
const selected = bar.value !== null && bar.value === value;
|
|
114
|
-
const named = bar.labels === 'all' || (bar.labels === 'selected' && selected);
|
|
115
|
-
const classNames = cx('flex min-w-0 flex-1 flex-col items-center justify-center px-1 py-1.5', itemGapClasses[bar.size], radiusClasses[bar.size], '[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]', transitionClasses,
|
|
116
|
-
// Inset rather than offset: the item is inside a sheet that clips, and a
|
|
117
|
-
// ring drawn outside it would have its top sliced off.
|
|
118
|
-
focusRingInsetClasses,
|
|
119
|
-
// An if/else rather than stacked variants: two Tailwind classes of equal
|
|
120
|
-
// specificity resolve by their order in the generated stylesheet.
|
|
121
|
-
disabled
|
|
122
|
-
? 'cursor-not-allowed opacity-50 saturate-[0.35] text-(--plass-muted-fg)'
|
|
123
|
-
: selected
|
|
124
|
-
? 'cursor-pointer font-medium text-(--p-accent) bg-(--p-soft) hover:bg-(--p-soft-hover)'
|
|
125
|
-
: 'cursor-pointer text-(--plass-muted-fg) hover:text-(--plass-fg) hover:bg-(--plass-glass-hover)', className);
|
|
126
|
-
const body = (_jsxs(_Fragment, { children: [hasContent(icon) ? (_jsx("span", { className: cx('flex shrink-0 items-center justify-center', iconSizeClasses[bar.size]), children: icon })) : null, hasContent(children) ? (_jsx("span", { className: named
|
|
127
|
-
? cx('max-w-full truncate leading-tight', metaTextClasses[bar.size])
|
|
128
|
-
: srOnlyClasses, children: children })) : null] }));
|
|
129
|
-
const press = (event) => {
|
|
130
|
-
if (disabled) {
|
|
131
|
-
event.preventDefault();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
bar.change(value);
|
|
135
|
-
onClick?.(event);
|
|
136
|
-
};
|
|
137
|
-
if (href) {
|
|
138
|
-
return (_jsx("a", { ref: ref, href: disabled ? undefined : href, "aria-current": selected ? 'page' : undefined, "aria-disabled": disabled || undefined, className: classNames, onClick: press, ...props, children: body }));
|
|
139
|
-
}
|
|
140
|
-
return (_jsx("button", { ref: ref, type: "button", disabled: disabled, "aria-current": selected ? 'page' : undefined, className: classNames, onClick: press, ...props, children: body }));
|
|
141
|
-
});
|
|
1
|
+
"use client";import{jsx as e,Fragment as a,jsxs as t}from"react/jsx-runtime";import*as l from"react";import{useRender as s}from"@base-ui/react/use-render";import{cx as i,focusRingInsetClasses as n,hasContent as r,iconSizeClasses as o,metaTextClasses as c,radiusClasses as d,sheetLineClasses as m,sheetRestClasses as u,srOnlyClasses as p,surfaceSlots as f,transitionClasses as g}from"../../internal/styles.js";const h=/* @__PURE__ */l.createContext({value:null,change:()=>{},size:"md",density:"default",labels:"all",disabled:!1}),x={static:"",sticky:"sticky bottom-0 z-20",fixed:"fixed inset-x-0 bottom-0 z-30"},v={xs:"min-h-10",sm:"min-h-12",md:"min-h-14",lg:"min-h-16",xl:"min-h-18"},b={default:{xs:"p-1",sm:"p-1",md:"p-1.5",lg:"p-2",xl:"p-2.5"},compact:{xs:"p-0.5",sm:"p-0.5",md:"p-1",lg:"p-1",xl:"p-1.5"}},y={xs:"gap-0.5",sm:"gap-0.5",md:"gap-1",lg:"gap-1",xl:"gap-1.5"};export const PlBottomNavigation=/* @__PURE__ */l.forwardRef(function PlBottomNavigation({variant:a="glass",size:t="md",color:n="primary",density:r="default",elevation:o=0,value:c,defaultValue:p=null,onValueChange:y,position:N="fixed",labels:w="all",divider:z=!0,safeArea:k=!0,disabled:C=!1,label:j,render:P,className:B,style:I,children:R,...V},A){const[D,F]=l.useState(p),M=void 0!==c,S=M?c:D,q=l.useCallback(e=>{M||F(e),y?.(e)},[M,y]),E=l.useMemo(()=>({value:S??null,change:q,size:t,density:r,labels:w,disabled:C}),[S,q,t,r,w,C]),G=i("w-full min-w-0","static"===N?d[t]:"",u[a],"glass"===a&&"static"!==N?"border-0":"",z?m:"",x[N],k?"pb-[env(safe-area-inset-bottom)]":"",g,B);return s({render:P??e("nav",{}),ref:A,props:{"aria-label":j,className:G,style:{...f(n,o),...I},children:e(h.Provider,{value:E,children:e("div",{className:i("flex w-full items-stretch",v[t],b[r][t]),children:R})}),...V}})});export const PlBottomNavigationItem=/* @__PURE__ */l.forwardRef(function PlBottomNavigationItem({value:s,icon:m,href:u,disabled:f=!1,className:x,children:v,onClick:b,...N},w){const z=l.useContext(h),k=f||z.disabled,C=null!==z.value&&z.value===s,j="all"===z.labels||"selected"===z.labels&&C,P=i("flex min-w-0 flex-1 flex-col items-center justify-center px-1 py-1.5",y[z.size],d[z.size],"[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]",g,n,k?"cursor-not-allowed opacity-50 saturate-[0.35] text-(--plass-muted-fg)":C?"cursor-pointer font-medium text-(--p-accent) bg-(--p-soft) hover:bg-(--p-soft-hover)":"cursor-pointer text-(--plass-muted-fg) hover:text-(--plass-fg) hover:bg-(--plass-glass-hover)",x),B=t(a,{children:[r(m)?e("span",{className:i("flex shrink-0 items-center justify-center",o[z.size]),children:m}):null,r(v)?e("span",{className:j?i("max-w-full truncate leading-tight",c[z.size]):p,children:v}):null]}),press=e=>{k?e.preventDefault():(z.change(s),b?.(e))};return u?e("a",{ref:w,href:k?void 0:u,"aria-current":C?"page":void 0,"aria-disabled":k||void 0,className:P,onClick:press,...N,children:B}):e("button",{ref:w,type:"button",disabled:k,"aria-current":C?"page":void 0,className:P,onClick:press,...N,children:B})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlBottomNavigation,PlBottomNavigationItem}from"./PlBottomNavigation.js";
|
|
@@ -1,46 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { useRender } from '@base-ui/react/use-render';
|
|
4
|
-
import { cx, radiusClasses, sheetPaddingXClasses, sheetPaddingYClasses, sheetRestClasses, surfaceSlots, transitionClasses } from '../../internal/styles.js';
|
|
5
|
-
/**
|
|
6
|
-
* A sheet of glass with content on it. The plainest surface in the library: it
|
|
7
|
-
* groups things, and that is all it does.
|
|
8
|
-
*
|
|
9
|
-
* Everything structural — a title, a footer, hairlines between sections —
|
|
10
|
-
* belongs to `PlCard`, which is a box with those sections laid out on it. What
|
|
11
|
-
* is left here is the sheet itself, and the reason it is worth having on its own
|
|
12
|
-
* is that most of what a screen groups has no heading: a well behind a form, a
|
|
13
|
-
* tile in a shelf, a panel round a chart.
|
|
14
|
-
*
|
|
15
|
-
* `size` means something different here from what it means on a control, and
|
|
16
|
-
* this is the one place in the library where that is true. A box is as tall as
|
|
17
|
-
* what it holds, and its children bring their own typography — a container that
|
|
18
|
-
* reset the type scale would render the same paragraph at two sizes depending
|
|
19
|
-
* on what it was wrapped in. So `size` is the size of the **sheet**: its radius
|
|
20
|
-
* and its padding, and nothing else.
|
|
21
|
-
*
|
|
22
|
-
* The three materials say what they say everywhere else, read as a container's:
|
|
23
|
-
* the sheet is never dyed, because what a box holds arrives with its own
|
|
24
|
-
* colours and tinting the pane under them puts every one on a background it was
|
|
25
|
-
* not chosen against. The family reaches the hairline and the focus ring and
|
|
26
|
-
* stops. `ghost` is the one to reach for inside another surface, where a second
|
|
27
|
-
* bordered rectangle is a second rectangle.
|
|
28
|
-
*/
|
|
29
|
-
export const PlBox = /* @__PURE__ */ React.forwardRef(function PlBox({ variant = 'glass', size = 'md', color = 'primary', density = 'default', elevation = 0, padded = true, render, className, style, children, ...props }, ref) {
|
|
30
|
-
const classNames = cx('block', radiusClasses[size], padded ? `${sheetPaddingXClasses[density][size]} ${sheetPaddingYClasses[density][size]}` : '', sheetRestClasses[variant],
|
|
31
|
-
// The same property list and duration as everything else, so a box whose
|
|
32
|
-
// colour or elevation changes settles at the house pace. There is no
|
|
33
|
-
// `:active` treatment because a box is not pressed — it holds things that
|
|
34
|
-
// are.
|
|
35
|
-
transitionClasses, className);
|
|
36
|
-
return useRender({
|
|
37
|
-
render: render ?? _jsx("div", {}),
|
|
38
|
-
ref,
|
|
39
|
-
props: {
|
|
40
|
-
className: classNames,
|
|
41
|
-
style: { ...surfaceSlots(color, elevation), ...style },
|
|
42
|
-
children,
|
|
43
|
-
...props
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
});
|
|
1
|
+
"use client";import{jsx as e}from"react/jsx-runtime";import*as r from"react";import{useRender as s}from"@base-ui/react/use-render";import{cx as t,radiusClasses as o,sheetPaddingXClasses as a,sheetPaddingYClasses as i,sheetRestClasses as n,surfaceSlots as l,transitionClasses as c}from"../../internal/styles.js";export const PlBox=/* @__PURE__ */r.forwardRef(function PlBox({variant:r="glass",size:d="md",color:m="primary",density:f="default",elevation:p=0,padded:u=!0,render:x,className:y,style:j,children:v,...b},h){const B=t("block",o[d],u?`${a[f][d]} ${i[f][d]}`:"",n[r],c,y);return s({render:x??e("div",{}),ref:h,props:{className:B,style:{...l(m,p),...j},children:v,...b}})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlBox}from"./PlBox.js";
|
|
@@ -1,201 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { ArrowRightIcon, ChevronIcon, EllipsisIcon } from '../../internal/icons.js';
|
|
4
|
-
import { controlTextClasses, cx, focusRingClasses, gapClasses, hasContent, iconClasses, radiusClasses, transitionClasses } from '../../internal/styles.js';
|
|
5
|
-
const BreadcrumbContext = /* @__PURE__ */ React.createContext({
|
|
6
|
-
size: 'md',
|
|
7
|
-
last: false
|
|
8
|
-
});
|
|
9
|
-
/** Between the steps, and the only thing `density` touches. */
|
|
10
|
-
const trailGapClasses = {
|
|
11
|
-
default: { xs: 'gap-1', sm: 'gap-1.5', md: 'gap-2', lg: 'gap-2.5', xl: 'gap-3' },
|
|
12
|
-
compact: { xs: 'gap-0.5', sm: 'gap-1', md: 'gap-1', lg: 'gap-1.5', xl: 'gap-2' }
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* A step's corner, read two steps down the radius ladder.
|
|
16
|
-
*
|
|
17
|
-
* `radiusClasses.md` is 12px, which on a line of text 20px tall is most of the
|
|
18
|
-
* way to a pill — and a breadcrumb step is not a `PlChip`. What the hover tint
|
|
19
|
-
* needs is a rectangle with the corners taken off, which is what the ladder says
|
|
20
|
-
* everywhere else; it just has to be read further down for something this short.
|
|
21
|
-
*/
|
|
22
|
-
const stepRadiusClasses = {
|
|
23
|
-
xs: radiusClasses.xs,
|
|
24
|
-
sm: radiusClasses.xs,
|
|
25
|
-
md: radiusClasses.xs,
|
|
26
|
-
lg: radiusClasses.sm,
|
|
27
|
-
xl: radiusClasses.sm
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* The four marks.
|
|
31
|
-
*
|
|
32
|
-
* The two that point are glyphs turned rather than redrawn, the same allowance
|
|
33
|
-
* the no-transform rule makes for the chevron on a `PlAccordion`: the wedge is
|
|
34
|
-
* drawn pointing down once, and a trail turns it a quarter. Both turn back under
|
|
35
|
-
* RTL, because a trail runs the way the language does.
|
|
36
|
-
*/
|
|
37
|
-
function separatorMark(separator) {
|
|
38
|
-
switch (separator) {
|
|
39
|
-
case 'chevron':
|
|
40
|
-
return (_jsx("span", { className: "flex -rotate-90 items-center rtl:rotate-90", children: _jsx(ChevronIcon, {}) }));
|
|
41
|
-
case 'arrow':
|
|
42
|
-
return (_jsx("span", { className: "flex items-center rtl:rotate-180", children: _jsx(ArrowRightIcon, {}) }));
|
|
43
|
-
case 'slash':
|
|
44
|
-
return _jsx("span", { className: "opacity-70", children: "/" });
|
|
45
|
-
case 'dot':
|
|
46
|
-
return _jsx("span", { className: "opacity-70", children: "\u00B7" });
|
|
47
|
-
default:
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const separatorNames = ['chevron', 'arrow', 'slash', 'dot'];
|
|
52
|
-
/**
|
|
53
|
-
* The words in a step, with everything that is not a word left out.
|
|
54
|
-
*
|
|
55
|
-
* A step's label is a `ReactNode` and `name` in the structured data is a string,
|
|
56
|
-
* so the tree is walked for its text. Only `children` is read — a `startIcon` is
|
|
57
|
-
* a picture of the thing rather than its name, and a home glyph contributing
|
|
58
|
-
* nothing is exactly right.
|
|
59
|
-
*/
|
|
60
|
-
function textOf(node) {
|
|
61
|
-
if (typeof node === 'string' || typeof node === 'number') {
|
|
62
|
-
return String(node);
|
|
63
|
-
}
|
|
64
|
-
if (Array.isArray(node)) {
|
|
65
|
-
return node.map(textOf).join('');
|
|
66
|
-
}
|
|
67
|
-
if (React.isValidElement(node)) {
|
|
68
|
-
return textOf(node.props.children);
|
|
69
|
-
}
|
|
70
|
-
return '';
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* A step's `href` as a search engine wants it: absolute.
|
|
74
|
-
*
|
|
75
|
-
* `new URL` is what resolves `../guide` against a base rather than pasting the
|
|
76
|
-
* two together, and it throws on anything it cannot make sense of — a base that
|
|
77
|
-
* is not a URL, an `href` that is a router-only path. Falling back to the `href`
|
|
78
|
-
* as written is better than dropping the entry: a relative URL is a weaker
|
|
79
|
-
* signal, an absent one is none.
|
|
80
|
-
*/
|
|
81
|
-
function absoluteHref(href, baseUrl) {
|
|
82
|
-
if (!baseUrl) {
|
|
83
|
-
return href;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
return new URL(href, baseUrl).href;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return href;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* The trail as `schema.org`'s `BreadcrumbList`.
|
|
94
|
-
*
|
|
95
|
-
* `item` is omitted where a step has no `href`, which is the last step's usual
|
|
96
|
-
* case: a crawler reads a final entry without one as the page being looked at,
|
|
97
|
-
* and a `ListItem` pointing at nothing would be worse than one pointing nowhere.
|
|
98
|
-
*/
|
|
99
|
-
function breadcrumbListData(steps, baseUrl) {
|
|
100
|
-
const itemListElement = steps.map((step, index) => {
|
|
101
|
-
const { href, children } = step.props;
|
|
102
|
-
return {
|
|
103
|
-
'@type': 'ListItem',
|
|
104
|
-
position: index + 1,
|
|
105
|
-
name: textOf(children),
|
|
106
|
-
...(href ? { item: absoluteHref(href, baseUrl) } : null)
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
// `<` is escaped because this string is written into a `<script>`: a label
|
|
110
|
-
// holding `</script>` would otherwise close the tag and turn the rest of the
|
|
111
|
-
// JSON into markup.
|
|
112
|
-
return JSON.stringify({
|
|
113
|
-
'@context': 'https://schema.org',
|
|
114
|
-
'@type': 'BreadcrumbList',
|
|
115
|
-
itemListElement
|
|
116
|
-
}).replace(/</g, '\\u003c');
|
|
117
|
-
}
|
|
118
|
-
function isSeparatorName(value) {
|
|
119
|
-
return typeof value === 'string' && separatorNames.includes(value);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* The trail of pages above the one being read.
|
|
123
|
-
*
|
|
124
|
-
* Two things make this more than a row of links. The first is that the last step
|
|
125
|
-
* is where the reader already is, so it is not a link at all — it carries
|
|
126
|
-
* `aria-current="page"` and stops being pressable, and the component works that
|
|
127
|
-
* out rather than asking every caller to remember it. The second is the fold: a
|
|
128
|
-
* trail seven levels deep is a trail nobody reads, so the middle collapses to a
|
|
129
|
-
* `…` that puts it back when pressed.
|
|
130
|
-
*
|
|
131
|
-
* The separators are drawn by the trail rather than by the steps. A step does
|
|
132
|
-
* not know whether anything follows it, and a mark that belonged to a step would
|
|
133
|
-
* have to be taken off the last one by hand.
|
|
134
|
-
*
|
|
135
|
-
* There is no `variant` and no `elevation`: a trail is a line of text above the
|
|
136
|
-
* page, not a surface laid on it.
|
|
137
|
-
*/
|
|
138
|
-
export const PlBreadcrumb = /* @__PURE__ */ React.forwardRef(function PlBreadcrumb({ size = 'md', color = 'primary', density = 'default', separator = 'chevron', maxItems, itemsBeforeCollapse = 1, itemsAfterCollapse = 1, expandable = true, label = 'Breadcrumb', expandLabel = 'Show the hidden steps', structuredData = false, baseUrl, className, style, children, ...props }, ref) {
|
|
139
|
-
const [unfolded, setUnfolded] = React.useState(false);
|
|
140
|
-
const steps = React.Children.toArray(children).filter(React.isValidElement);
|
|
141
|
-
const total = steps.length;
|
|
142
|
-
/*
|
|
143
|
-
* The last step is the page you are on — unless a step says it is. Exactly one
|
|
144
|
-
* element in a trail may carry `aria-current="page"`, so a caller who marks an
|
|
145
|
-
* earlier step has to take the mark off the last one, and doing that by hand
|
|
146
|
-
* would mean writing `current={false}` on a step that never asked for it.
|
|
147
|
-
*/
|
|
148
|
-
const claimed = steps.some((step) => step.props.current === true);
|
|
149
|
-
const folding = !unfolded &&
|
|
150
|
-
maxItems !== undefined &&
|
|
151
|
-
total > Math.max(maxItems, 1) &&
|
|
152
|
-
// A fold has to actually remove something. With `1` before and `1` after on
|
|
153
|
-
// a three-step trail the `…` would stand in for exactly one step, which is
|
|
154
|
-
// longer than the step it replaced.
|
|
155
|
-
total - itemsBeforeCollapse - itemsAfterCollapse > 1;
|
|
156
|
-
const shown = folding
|
|
157
|
-
? [
|
|
158
|
-
...steps.slice(0, Math.max(0, itemsBeforeCollapse)),
|
|
159
|
-
null,
|
|
160
|
-
...steps.slice(total - Math.max(0, itemsAfterCollapse))
|
|
161
|
-
]
|
|
162
|
-
: steps;
|
|
163
|
-
const mark = isSeparatorName(separator) ? separatorMark(separator) : separator;
|
|
164
|
-
const foldClassNames = cx('inline-flex items-center rounded-(--plass-radius-xs) px-0.5', 'text-(--plass-muted-fg)', transitionClasses, iconClasses, expandable
|
|
165
|
-
? cx('cursor-pointer hover:bg-(--p-soft) hover:text-(--p-accent)', focusRingClasses)
|
|
166
|
-
: '');
|
|
167
|
-
return (_jsxs("nav", { ref: ref, "aria-label": label, className: cx('flex', controlTextClasses[size], iconClasses, className), style: {
|
|
168
|
-
'--p-accent': `var(--plass-${color}-accent)`,
|
|
169
|
-
'--p-soft': `var(--plass-${color}-soft)`,
|
|
170
|
-
'--p-ring': `var(--plass-${color}-ring)`,
|
|
171
|
-
...style
|
|
172
|
-
}, ...props, children: [structuredData && total > 0 ? (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: breadcrumbListData(steps, baseUrl) } })) : null, _jsx("ol", {
|
|
173
|
-
// `role="list"` said out loud: Tailwind's reset takes the bullets off
|
|
174
|
-
// every `<ol>`, and Safari takes the list semantics off with them.
|
|
175
|
-
role: "list", className: cx('flex list-none flex-wrap items-center p-0', trailGapClasses[density][size]), children: shown.map((step, index) => (_jsxs(React.Fragment, { children: [index > 0 ? (_jsx("li", { "aria-hidden": "true", className: "flex shrink-0 items-center text-(--plass-muted-fg) select-none", children: mark })) : null, step ? (_jsx(BreadcrumbContext.Provider, { value: { size, last: !claimed && index === shown.length - 1 }, children: step })) : (_jsx("li", { className: "flex shrink-0 items-center", children: expandable ? (_jsx("button", { type: "button", className: foldClassNames, "aria-label": expandLabel, onClick: () => setUnfolded(true), children: _jsx(EllipsisIcon, {}) })) : (_jsx("span", { className: foldClassNames, "aria-hidden": "true", children: _jsx(EllipsisIcon, {}) })) }))] }, step ? (step.key ?? index) : 'fold'))) })] }));
|
|
176
|
-
});
|
|
177
|
-
/**
|
|
178
|
-
* One step of the trail.
|
|
179
|
-
*
|
|
180
|
-
* It renders three different things and the caller picks by what they pass: an
|
|
181
|
-
* `<a>` with an `href`, a `<button>` with an `onClick`, and a plain `<span>` with
|
|
182
|
-
* neither — which is what the last step is, because the page you are already on
|
|
183
|
-
* is not somewhere to go.
|
|
184
|
-
*/
|
|
185
|
-
export const PlBreadcrumbItem = /* @__PURE__ */ React.forwardRef(function PlBreadcrumbItem({ href, onClick, startIcon, endIcon, current, disabled = false, className, children, ...props }, ref) {
|
|
186
|
-
const { size, last } = React.useContext(BreadcrumbContext);
|
|
187
|
-
const isCurrent = current ?? last;
|
|
188
|
-
const interactive = Boolean(href || onClick) && !isCurrent && !disabled;
|
|
189
|
-
const stepClassNames = cx('inline-flex min-w-0 items-center px-1', gapClasses[size], stepRadiusClasses[size], transitionClasses,
|
|
190
|
-
// An if/else rather than stacked variants: two Tailwind classes of equal
|
|
191
|
-
// specificity resolve by their order in the generated stylesheet.
|
|
192
|
-
disabled
|
|
193
|
-
? 'cursor-not-allowed text-(--plass-muted-fg) opacity-50'
|
|
194
|
-
: isCurrent
|
|
195
|
-
? 'font-medium text-(--plass-fg)'
|
|
196
|
-
: interactive
|
|
197
|
-
? cx('cursor-pointer text-(--plass-muted-fg)', 'hover:bg-(--p-soft) hover:text-(--p-accent)', focusRingClasses)
|
|
198
|
-
: 'text-(--plass-muted-fg)');
|
|
199
|
-
const body = (_jsxs(_Fragment, { children: [hasContent(startIcon) ? (_jsx("span", { className: "flex h-[1lh] shrink-0 items-center", children: startIcon })) : null, _jsx("span", { className: "truncate", children: children }), hasContent(endIcon) ? (_jsx("span", { className: "flex h-[1lh] shrink-0 items-center", children: endIcon })) : null] }));
|
|
200
|
-
return (_jsx("li", { ref: ref, className: cx('flex min-w-0 items-center', className), ...props, children: interactive && href ? (_jsx("a", { href: href, className: stepClassNames, onClick: onClick, children: body })) : interactive ? (_jsx("button", { type: "button", className: stepClassNames, onClick: onClick, children: body })) : (_jsx("span", { className: stepClassNames, "aria-current": isCurrent ? 'page' : undefined, "aria-disabled": disabled || undefined, children: body })) }));
|
|
201
|
-
});
|
|
1
|
+
"use client";import{jsx as e,jsxs as t,Fragment as r}from"react/jsx-runtime";import*as a from"react";import{ArrowRightIcon as s,ChevronIcon as n,EllipsisIcon as l}from"../../internal/icons.js";import{controlTextClasses as i,cx as c,focusRingClasses as o,gapClasses as m,hasContent as p,iconClasses as d,radiusClasses as u,transitionClasses as f}from"../../internal/styles.js";const h=/* @__PURE__ */a.createContext({size:"md",last:!1}),x={default:{xs:"gap-1",sm:"gap-1.5",md:"gap-2",lg:"gap-2.5",xl:"gap-3"},compact:{xs:"gap-0.5",sm:"gap-1",md:"gap-1",lg:"gap-1.5",xl:"gap-2"}},g={xs:u.xs,sm:u.xs,md:u.xs,lg:u.sm,xl:u.sm},b=["chevron","arrow","slash","dot"];function textOf(e){return"string"==typeof e||"number"==typeof e?String(e):Array.isArray(e)?e.map(textOf).join(""):a.isValidElement(e)?textOf(e.props.children):""}function absoluteHref(e,t){if(!t)return e;try{return new URL(e,t).href}catch{return e}}function breadcrumbListData(e,t){const r=e.map((e,r)=>{const{href:a,children:s}=e.props;return{"@type":"ListItem",position:r+1,name:textOf(s),...a?{item:absoluteHref(a,t)}:null}});return JSON.stringify({"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:r}).replace(/</g,"\\u003c")}export const PlBreadcrumb=/* @__PURE__ */a.forwardRef(function PlBreadcrumb({size:r="md",color:m="primary",density:p="default",separator:u="chevron",maxItems:g,itemsBeforeCollapse:y=1,itemsAfterCollapse:N=1,expandable:v=!0,label:w="Breadcrumb",expandLabel:k="Show the hidden steps",structuredData:C=!1,baseUrl:B,className:L,style:j,children:I,...S},M){const[O,P]=a.useState(!1),z=a.Children.toArray(I).filter(a.isValidElement),A=z.length,D=z.some(e=>!0===e.props.current),E=!O&&void 0!==g&&A>Math.max(g,1)&&A-y-N>1?[...z.slice(0,Math.max(0,y)),null,...z.slice(A-Math.max(0,N))]:z,H=function isSeparatorName(e){return"string"==typeof e&&b.includes(e)}(u)?function separatorMark(t){switch(t){case"chevron":return e("span",{className:"flex -rotate-90 items-center rtl:rotate-90",children:e(n,{})});case"arrow":return e("span",{className:"flex items-center rtl:rotate-180",children:e(s,{})});case"slash":return e("span",{className:"opacity-70",children:"/"});case"dot":return e("span",{className:"opacity-70",children:"·"});default:return null}}(u):u,R=c("inline-flex items-center rounded-(--plass-radius-xs) px-0.5","text-(--plass-muted-fg)",f,d,v?c("cursor-pointer hover:bg-(--p-soft) hover:text-(--p-accent)",o):"");return t("nav",{ref:M,"aria-label":w,className:c("flex",i[r],d,L),style:{"--p-accent":`var(--plass-${m}-accent)`,"--p-soft":`var(--plass-${m}-soft)`,"--p-ring":`var(--plass-${m}-ring)`,...j},...S,children:[C&&A>0?e("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:breadcrumbListData(z,B)}}):null,e("ol",{role:"list",className:c("flex list-none flex-wrap items-center p-0",x[p][r]),children:E.map((s,n)=>t(a.Fragment,{children:[n>0?e("li",{"aria-hidden":"true",className:"flex shrink-0 items-center text-(--plass-muted-fg) select-none",children:H}):null,s?e(h.Provider,{value:{size:r,last:!D&&n===E.length-1},children:s}):e("li",{className:"flex shrink-0 items-center",children:v?e("button",{type:"button",className:R,"aria-label":k,onClick:()=>P(!0),children:e(l,{})}):e("span",{className:R,"aria-hidden":"true",children:e(l,{})})})]},s?s.key??n:"fold"))})]})});export const PlBreadcrumbItem=/* @__PURE__ */a.forwardRef(function PlBreadcrumbItem({href:s,onClick:n,startIcon:l,endIcon:i,current:d,disabled:u=!1,className:x,children:b,...y},N){const{size:v,last:w}=a.useContext(h),k=d??w,C=Boolean(s||n)&&!k&&!u,B=c("inline-flex min-w-0 items-center px-1",m[v],g[v],f,u?"cursor-not-allowed text-(--plass-muted-fg) opacity-50":k?"font-medium text-(--plass-fg)":C?c("cursor-pointer text-(--plass-muted-fg)","hover:bg-(--p-soft) hover:text-(--p-accent)",o):"text-(--plass-muted-fg)"),L=t(r,{children:[p(l)?e("span",{className:"flex h-[1lh] shrink-0 items-center",children:l}):null,e("span",{className:"truncate",children:b}),p(i)?e("span",{className:"flex h-[1lh] shrink-0 items-center",children:i}):null]});return e("li",{ref:N,className:c("flex min-w-0 items-center",x),...y,children:C&&s?e("a",{href:s,className:B,onClick:n,children:L}):C?e("button",{type:"button",className:B,onClick:n,children:L}):e("span",{className:B,"aria-current":k?"page":void 0,"aria-disabled":u||void 0,children:L})})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlBreadcrumb,PlBreadcrumbItem}from"./PlBreadcrumb.js";
|
|
@@ -1,196 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { Button as BaseUIButton } from '@base-ui/react/button';
|
|
4
|
-
import { useRender } from '@base-ui/react/use-render';
|
|
5
|
-
import { ButtonGroupContext } from '../../internal/button-group.js';
|
|
6
|
-
import { Spinner } from '../../internal/icons.js';
|
|
7
|
-
import { controlHeightClasses, controlSlots, controlSquareClasses, controlTextClasses, disabledClasses, focusRingClasses, gapClasses, glassClasses, hasContent, iconClasses, paddingXClasses, radiusClasses, readOnlyFilterClasses, transitionClasses } from '../../internal/styles.js';
|
|
8
|
-
/**
|
|
9
|
-
* The scales all come from `internal/styles` — a button's height *is* the
|
|
10
|
-
* library's control height, and the same numbers have to hold on a PlTextField,
|
|
11
|
-
* a Select and a Chip for a mixed row to keep its baseline.
|
|
12
|
-
*/
|
|
13
|
-
const sizeClasses = {
|
|
14
|
-
xs: `${controlHeightClasses.xs} ${gapClasses.xs} ${radiusClasses.xs} ${controlTextClasses.xs}`,
|
|
15
|
-
sm: `${controlHeightClasses.sm} ${gapClasses.sm} ${radiusClasses.sm} ${controlTextClasses.sm}`,
|
|
16
|
-
md: `${controlHeightClasses.md} ${gapClasses.md} ${radiusClasses.md} ${controlTextClasses.md}`,
|
|
17
|
-
lg: `${controlHeightClasses.lg} ${gapClasses.lg} ${radiusClasses.lg} ${controlTextClasses.lg}`,
|
|
18
|
-
xl: `${controlHeightClasses.xl} ${gapClasses.xl} ${radiusClasses.xl} ${controlTextClasses.xl}`
|
|
19
|
-
};
|
|
20
|
-
/** With no label there is nothing to pad against, so the button goes square. */
|
|
21
|
-
const iconOnlyClasses = {
|
|
22
|
-
xs: `${controlSquareClasses.xs} px-0`,
|
|
23
|
-
sm: `${controlSquareClasses.sm} px-0`,
|
|
24
|
-
md: `${controlSquareClasses.md} px-0`,
|
|
25
|
-
lg: `${controlSquareClasses.lg} px-0`,
|
|
26
|
-
xl: `${controlSquareClasses.xl} px-0`
|
|
27
|
-
};
|
|
28
|
-
const baseClasses = /* @__PURE__ */ [
|
|
29
|
-
// `relative` because `.plass-glow` hangs its two light layers off `::before`/`::after`.
|
|
30
|
-
'relative inline-flex shrink-0 select-none items-center justify-center',
|
|
31
|
-
'whitespace-nowrap align-middle font-semibold leading-none',
|
|
32
|
-
'[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]',
|
|
33
|
-
transitionClasses,
|
|
34
|
-
focusRingClasses,
|
|
35
|
-
iconClasses
|
|
36
|
-
].join(' ');
|
|
37
|
-
/**
|
|
38
|
-
* The three materials.
|
|
39
|
-
*
|
|
40
|
-
* `solid` is the only one with a `background-image`, and that gradient is the
|
|
41
|
-
* whole of the form: two ends of the family swept across the control, with no
|
|
42
|
-
* highlight over the top of it. A filled control deliberately carries **no**
|
|
43
|
-
* gloss line — an inset white edge on a coloured surface is what reads as
|
|
44
|
-
* lacquer, and the sweep is already saying everything about the shape that
|
|
45
|
-
* needs saying. The hairline belongs to `glass`, which has a real cut edge.
|
|
46
|
-
*
|
|
47
|
-
* `glass` wears the family in its **text** rather than in its sheet, which is
|
|
48
|
-
* why `color="secondary"` is the neutral, quiet button and not a second grey
|
|
49
|
-
* variant nobody would remember the name of.
|
|
50
|
-
*/
|
|
51
|
-
const restClasses = {
|
|
52
|
-
solid: /* @__PURE__ */ [
|
|
53
|
-
'text-(--p-on-solid) [background-image:var(--p-fill)]',
|
|
54
|
-
'[box-shadow:var(--p-elev),var(--p-lift)]'
|
|
55
|
-
].join(' '),
|
|
56
|
-
glass: /* @__PURE__ */ [
|
|
57
|
-
glassClasses,
|
|
58
|
-
'border text-(--p-accent) bg-(--plass-glass)',
|
|
59
|
-
'[border-color:var(--plass-glass-line)]',
|
|
60
|
-
'[box-shadow:var(--p-elev),var(--plass-gloss-glass)]'
|
|
61
|
-
].join(' '),
|
|
62
|
-
// Nothing to catch the light on, and nothing to cast a shadow.
|
|
63
|
-
ghost: 'text-(--p-accent) bg-transparent'
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Hover lifts the control and turns the light up; press puts it down and turns
|
|
67
|
-
* the light down. Both are `filter: brightness()` rather than a second set of
|
|
68
|
-
* colours, because the fill is a gradient and a gradient cannot be transitioned
|
|
69
|
-
* — but light falling on one can. The pointer bloom `.plass-glow` draws rides
|
|
70
|
-
* on top of this; the brightness is the state, the bloom is where the pointer
|
|
71
|
-
* is.
|
|
72
|
-
*/
|
|
73
|
-
const hoverClasses = {
|
|
74
|
-
solid: /* @__PURE__ */ [
|
|
75
|
-
'hover:brightness-105',
|
|
76
|
-
'hover:[box-shadow:var(--p-elev-hover),var(--p-lift-hover)]',
|
|
77
|
-
'active:brightness-95',
|
|
78
|
-
'active:[box-shadow:var(--p-elev-press),var(--p-lift-press)]'
|
|
79
|
-
].join(' '),
|
|
80
|
-
glass: /* @__PURE__ */ [
|
|
81
|
-
'hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line)]',
|
|
82
|
-
'hover:[box-shadow:var(--p-elev-hover),var(--plass-gloss-glass)]',
|
|
83
|
-
'active:bg-(--plass-glass-press)',
|
|
84
|
-
'active:[box-shadow:var(--p-elev-press),var(--plass-gloss-glass)]'
|
|
85
|
-
].join(' '),
|
|
86
|
-
ghost: 'hover:bg-(--p-soft) active:bg-(--p-soft-hover)'
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Read-only keeps the shape, the colour and the edge but goes flat, loses its
|
|
90
|
-
* lift and drains most of the saturation.
|
|
91
|
-
*/
|
|
92
|
-
const readOnlyClasses = {
|
|
93
|
-
solid: /* @__PURE__ */ [
|
|
94
|
-
'cursor-default text-(--p-on-solid) [background-image:var(--p-fill)]',
|
|
95
|
-
`shadow-none ${readOnlyFilterClasses}`
|
|
96
|
-
].join(' '),
|
|
97
|
-
glass: /* @__PURE__ */ [
|
|
98
|
-
glassClasses,
|
|
99
|
-
'cursor-default border text-(--p-accent) bg-(--plass-glass)',
|
|
100
|
-
'[border-color:var(--plass-glass-line)]',
|
|
101
|
-
`[box-shadow:var(--plass-gloss-glass)] ${readOnlyFilterClasses}`
|
|
102
|
-
].join(' '),
|
|
103
|
-
ghost: `cursor-default text-(--p-accent) bg-transparent ${readOnlyFilterClasses}`
|
|
104
|
-
};
|
|
105
|
-
export const PlButton = /* @__PURE__ */ React.forwardRef(function PlButton({ variant: variantProp, size: sizeProp, color: colorProp, density: densityProp, elevation: elevationProp, startIcon, endIcon, loading = false, readOnly = false, fullWidth = false, disabled: disabledProp, render, className, style, children, onClick, onPointerMove, ...props }, ref) {
|
|
106
|
-
/*
|
|
107
|
-
* A `PlButtonGroup` around this button sets the axes once for the set. The
|
|
108
|
-
* button's own prop still wins — a row of secondary actions with one danger
|
|
109
|
-
* button in it is a real thing — and with no group around it the fallbacks
|
|
110
|
-
* are the defaults they always were.
|
|
111
|
-
*/
|
|
112
|
-
const group = React.useContext(ButtonGroupContext);
|
|
113
|
-
const variant = variantProp ?? group?.variant ?? 'solid';
|
|
114
|
-
const size = sizeProp ?? group?.size ?? 'md';
|
|
115
|
-
const color = colorProp ?? group?.color ?? 'primary';
|
|
116
|
-
const density = densityProp ?? group?.density ?? 'default';
|
|
117
|
-
const elevation = elevationProp ?? group?.elevation ?? 1;
|
|
118
|
-
const disabled = disabledProp ?? group?.disabled ?? false;
|
|
119
|
-
const iconOnly = !hasContent(children);
|
|
120
|
-
// `disabled` and `readOnly` change how the button looks; `loading` only stops
|
|
121
|
-
// it from firing.
|
|
122
|
-
const inert = loading || readOnly;
|
|
123
|
-
const interactive = !disabled && !inert;
|
|
124
|
-
const classNames = [
|
|
125
|
-
baseClasses,
|
|
126
|
-
sizeClasses[size],
|
|
127
|
-
iconOnly ? iconOnlyClasses[size] : paddingXClasses[density][size],
|
|
128
|
-
// Deliberately an if/else rather than stacked `data-*` variants: two
|
|
129
|
-
// Tailwind variants of equal specificity resolve by their order in the
|
|
130
|
-
// generated stylesheet, which is not something a component should depend on.
|
|
131
|
-
disabled
|
|
132
|
-
? disabledClasses[variant]
|
|
133
|
-
: readOnly
|
|
134
|
-
? readOnlyClasses[variant]
|
|
135
|
-
: restClasses[variant],
|
|
136
|
-
interactive ? `${hoverClasses[variant]} cursor-pointer` : '',
|
|
137
|
-
// The interaction light. Every variant takes it, because it is about where
|
|
138
|
-
// the pointer is rather than about what the surface is made of — the two
|
|
139
|
-
// colour slots behind it are what switch with the variant.
|
|
140
|
-
interactive ? 'plass-glow' : '',
|
|
141
|
-
loading ? 'cursor-progress' : '',
|
|
142
|
-
fullWidth ? 'w-full' : '',
|
|
143
|
-
className ?? ''
|
|
144
|
-
]
|
|
145
|
-
.filter(Boolean)
|
|
146
|
-
.join(' ');
|
|
147
|
-
/*
|
|
148
|
-
* `render` deliberately steps around Base UI's PlButton rather than being handed
|
|
149
|
-
* to it. Told to render a non-`<button>`, that component puts `role="button"`
|
|
150
|
-
* on whatever it was given — which is right for a `<div>` and wrong for the
|
|
151
|
-
* case this prop exists for: an `<a href>` under a `role="button"` stops being
|
|
152
|
-
* a link to everything that reads the page, and the link list, the status bar
|
|
153
|
-
* and the crawler all lose it.
|
|
154
|
-
*
|
|
155
|
-
* What Base UI's PlButton adds over a bare `<button>` is its disabled handling,
|
|
156
|
-
* and `disabled` is the one thing that cannot travel to an `<a>` anyway.
|
|
157
|
-
*/
|
|
158
|
-
return useRender({
|
|
159
|
-
render: render ?? _jsx(BaseUIButton, { disabled: disabled }),
|
|
160
|
-
ref,
|
|
161
|
-
props: {
|
|
162
|
-
className: classNames,
|
|
163
|
-
style: { ...controlSlots(color, elevation, variant), ...style },
|
|
164
|
-
'aria-disabled': inert || undefined,
|
|
165
|
-
'aria-busy': loading || undefined,
|
|
166
|
-
'data-loading': loading || undefined,
|
|
167
|
-
'data-readonly': readOnly || undefined,
|
|
168
|
-
onClick: (event) => {
|
|
169
|
-
if (inert) {
|
|
170
|
-
event.preventDefault();
|
|
171
|
-
event.stopPropagation();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
onClick?.(event);
|
|
175
|
-
},
|
|
176
|
-
onPointerMove: (event) => {
|
|
177
|
-
// Feeds the two light layers in `styles.css`. Written straight to the
|
|
178
|
-
// element rather than held in state: this fires at pointer rate, and a
|
|
179
|
-
// `setState` here would re-render the tree on every mouse move. Reading
|
|
180
|
-
// `offsetX/offsetY` costs nothing — no `getBoundingClientRect`, so no
|
|
181
|
-
// forced layout. Icons carry `pointer-events: none`, so the offsets are
|
|
182
|
-
// always relative to the button itself.
|
|
183
|
-
//
|
|
184
|
-
// It runs while a finger is down too, which is what makes the light
|
|
185
|
-
// follow a drag on a touch screen — there is no hover there, and the
|
|
186
|
-
// `:active` layer is the one doing the work.
|
|
187
|
-
const element = event.currentTarget;
|
|
188
|
-
element.style.setProperty('--p-mx', `${event.nativeEvent.offsetX}px`);
|
|
189
|
-
element.style.setProperty('--p-my', `${event.nativeEvent.offsetY}px`);
|
|
190
|
-
onPointerMove?.(event);
|
|
191
|
-
},
|
|
192
|
-
...props,
|
|
193
|
-
children: (_jsxs(_Fragment, { children: [loading ? _jsx(Spinner, {}) : startIcon, children, endIcon] }))
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
});
|
|
1
|
+
"use client";import{jsx as s,Fragment as e,jsxs as o}from"react/jsx-runtime";import*as r from"react";import{Button as a}from"@base-ui/react/button";import{useRender as t}from"@base-ui/react/use-render";import{ButtonGroupContext as l}from"../../internal/button-group.js";import{Spinner as n}from"../../internal/icons.js";import{controlHeightClasses as i,controlSlots as p,controlSquareClasses as d,controlTextClasses as v,disabledClasses as c,focusRingClasses as g,gapClasses as m,glassClasses as x,hasContent as b,iconClasses as f,paddingXClasses as h,radiusClasses as u,readOnlyFilterClasses as $,transitionClasses as j}from"../../internal/styles.js";const y={xs:`${i.xs} ${m.xs} ${u.xs} ${v.xs}`,sm:`${i.sm} ${m.sm} ${u.sm} ${v.sm}`,md:`${i.md} ${m.md} ${u.md} ${v.md}`,lg:`${i.lg} ${m.lg} ${u.lg} ${v.lg}`,xl:`${i.xl} ${m.xl} ${u.xl} ${v.xl}`},w={xs:`${d.xs} px-0`,sm:`${d.sm} px-0`,md:`${d.md} px-0`,lg:`${d.lg} px-0`,xl:`${d.xl} px-0`},P=/* @__PURE__ */["relative inline-flex shrink-0 select-none items-center justify-center","whitespace-nowrap align-middle font-semibold leading-none","[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]",j,g,f].join(" "),k={solid:/* @__PURE__ */["text-(--p-on-solid) [background-image:var(--p-fill)]","[box-shadow:var(--p-elev),var(--p-lift)]"].join(" "),glass:/* @__PURE__ */[x,"border text-(--p-accent) bg-(--plass-glass)","[border-color:var(--plass-glass-line)]","[box-shadow:var(--p-elev),var(--plass-gloss-glass)]"].join(" "),ghost:"text-(--p-accent) bg-transparent"},B={solid:/* @__PURE__ */["hover:brightness-105","hover:[box-shadow:var(--p-elev-hover),var(--p-lift-hover)]","active:brightness-95","active:[box-shadow:var(--p-elev-press),var(--p-lift-press)]"].join(" "),glass:/* @__PURE__ */["hover:bg-(--plass-glass-hover) hover:[border-color:var(--p-line)]","hover:[box-shadow:var(--p-elev-hover),var(--plass-gloss-glass)]","active:bg-(--plass-glass-press)","active:[box-shadow:var(--p-elev-press),var(--plass-gloss-glass)]"].join(" "),ghost:"hover:bg-(--p-soft) active:bg-(--p-soft-hover)"},C={solid:/* @__PURE__ */["cursor-default text-(--p-on-solid) [background-image:var(--p-fill)]",`shadow-none ${$}`].join(" "),glass:/* @__PURE__ */[x,"cursor-default border text-(--p-accent) bg-(--plass-glass)","[border-color:var(--plass-glass-line)]",`[box-shadow:var(--plass-gloss-glass)] ${$}`].join(" "),ghost:`cursor-default text-(--p-accent) bg-transparent ${$}`};export const PlButton=/* @__PURE__ */r.forwardRef(function PlButton({variant:i,size:d,color:v,density:g,elevation:m,startIcon:x,endIcon:f,loading:u=!1,readOnly:$=!1,fullWidth:j=!1,disabled:z,render:E,className:I,style:M,children:N,onClick:D,onPointerMove:F,...O},R){const T=r.useContext(l),W=i??T?.variant??"solid",X=d??T?.size??"md",Y=v??T?.color??"primary",q=g??T?.density??"default",A=m??T?.elevation??1,G=z??T?.disabled??!1,H=!b(N),J=u||$,K=!G&&!J,L=[P,y[X],H?w[X]:h[q][X],G?c[W]:$?C[W]:k[W],K?`${B[W]} cursor-pointer`:"",K?"plass-glow":"",u?"cursor-progress":"",j?"w-full":"",I??""].filter(Boolean).join(" ");return t({render:E??s(a,{disabled:G}),ref:R,props:{className:L,style:{...p(Y,A,W),...M},"aria-disabled":J||void 0,"aria-busy":u||void 0,"data-loading":u||void 0,"data-readonly":$||void 0,onClick:s=>{if(J)return s.preventDefault(),void s.stopPropagation();D?.(s)},onPointerMove:s=>{const e=s.currentTarget;e.style.setProperty("--p-mx",`${s.nativeEvent.offsetX}px`),e.style.setProperty("--p-my",`${s.nativeEvent.offsetY}px`),F?.(s)},...O,children:o(e,{children:[u?s(n,{}):x,N,f]})}})});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export{PlButton}from"./PlButton.js";
|