neba 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,9 +109,9 @@ Placement props are logical, not physical — `start`/`end` rather than `left`/`
109
109
 
110
110
  ### Components
111
111
 
112
- **Inputs** — Button, IconButton, ButtonGroup, SegmentedButton, FloatingActionButton (with FloatingAction), TextField, NumberField, OtpField, Select, Combobox, Checkbox, RadioGroup, Switch, Slider, Rating, Menu (with submenus, checkbox and radio items), ContextMenu, FilePicker, Pagination, BottomNavigation, ColorPicker, DatePicker, TimePicker, DateTimePicker, DateRangePicker
112
+ **Inputs** — Button, IconButton, ButtonGroup, SegmentedButton, FloatingActionButton (with FloatingAction), TextField, NumberField, OtpField, Select, Combobox, Checkbox, RadioGroup, Switch, Slider, Rating, Menu (with submenus, checkbox and radio items), ContextMenu, FilePicker, Pagination, BottomNavigation, FloatingBottomNavigation, ColorPicker, DatePicker, TimePicker, DateTimePicker, DateRangePicker
113
113
 
114
- **Surfaces** — Box, Card, Accordion, Collapsible, Tabs, Carousel, Toolbar, Pill, Spoiler, ChatBubble, Drawer, Popover, Mockup
114
+ **Surfaces** — Box, Card, Accordion, Collapsible, Tabs, Carousel, Toolbar, Pill, Spoiler, ChatBubble, Drawer, Popover, Mockup, WindowPane
115
115
 
116
116
  **Display** — Typography, TextLink, Blockquote, Highlight, Divider, Chip, Badge, Avatar, Icon, Shortcut, List, Table, DataTable, Timeline, Breadcrumb, TreeView
117
117
 
@@ -119,11 +119,11 @@ Placement props are logical, not physical — `start`/`end` rather than `left`/`
119
119
 
120
120
  **Feedback** — Alert, Dialog, Toast, Tooltip, Overlay, Skeleton, Empty, ProgressLinear, ProgressCircular, ProgressBox
121
121
 
122
- **Layout** — Container, Grid (with GridContainer), Panes, AspectRatio
122
+ **Layout** — Container, Grid (with GridContainer), Panes, AspectRatio, ScrollZone
123
123
 
124
124
  **Transitions** — AnimateFade, AnimateGrow, AnimateZoom, AnimateSlide, AnimateRotate, AnimateBlink, AnimateAppear, AnimateTyping, AnimateLighting, AnimateMarquee, AnimateHeadline
125
125
 
126
- **Recently added** — Collapsible, Rating, BottomNavigation, FloatingActionButton, the charts (Sparkline, LineChart, AreaChart, BarChart, PieChart, ScatterChart, TimelineChart, HeatmapChart), DataTable, Empty, Mockup, the eleven `Animate*` wrappers, ColorPicker, Drawer, Popover, Skeleton, AspectRatio
126
+ **Recently added** — ScrollZone, FloatingBottomNavigation, WindowPane, Collapsible, Rating, BottomNavigation, FloatingActionButton, the charts (Sparkline, LineChart, AreaChart, BarChart, PieChart, ScatterChart, TimelineChart, HeatmapChart), DataTable, Empty, Mockup, the eleven `Animate*` wrappers, ColorPicker, Drawer, Popover, Skeleton, AspectRatio
127
127
 
128
128
  Each one has its own page — live previews, every prop, and the variations worth seeing — under [**All components**](https://neba.cdget.com/components/).
129
129
 
@@ -1,20 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { useRender } from '@base-ui/react/use-render';
3
+ import type { BottomNavigationLabels, BottomNavigationValue } from '../../internal/bottom-navigation';
3
4
  import type { NebaElevation, NebaPosition, NebaStyleProps } from '../../types';
4
- /** A destination's value. The same restraint Tabs and SegmentedButton put on theirs. */
5
- export type BottomNavigationValue = string | number;
6
- /**
7
- * Which labels are drawn.
8
- *
9
- * - `all` — every destination is named. The default, and the only one that
10
- * works for a reader who has not used the app before.
11
- * - `selected` — only the destination that is current. The bar keeps its height
12
- * either way, because the named one is always the tallest; what changes is
13
- * how much of the row is words. The names the other items lose are still in
14
- * the document for a screen reader.
15
- * - `none` — glyphs only, with every name read out but never drawn.
16
- */
17
- export type BottomNavigationLabels = 'all' | 'selected' | 'none';
5
+ export type { BottomNavigationLabels, BottomNavigationValue } from '../../internal/bottom-navigation';
18
6
  export interface BottomNavigationProps extends NebaStyleProps, Omit<React.ComponentPropsWithoutRef<'nav'>, 'color' | 'defaultValue' | 'onChange'> {
19
7
  /** The destination the reader is on. Use with `onValueChange` for a controlled bar. */
20
8
  value?: BottomNavigationValue | null;
@@ -1 +1 @@
1
- import{jsx as e,Fragment as a,jsxs as t}from"react/jsx-runtime";import*as l from"react";import{useRender as n}from"@base-ui/react/use-render";import{cx as i,hasContent as s,iconSizeClasses as r,metaTextClasses as o,radiusClasses as d,srOnlyClasses as c,surfaceClasses as u,surfaceSlots as m,transitionClasses as f}from"../../internal/styles";const p=l.createContext({value:null,change:()=>{},size:"md",density:"default",labels:"all",disabled:!1}),b={solid:[u,"text-(--neba-fg) bg-(--n-panel-hover)","[box-shadow:var(--n-elev),var(--neba-plate-solid)]"].join(" "),outline:[u,"text-(--neba-fg) bg-(--n-panel)","[box-shadow:var(--n-elev),var(--neba-plate-glass)]"].join(" "),text:"text-(--neba-fg) bg-transparent"},v={static:"",sticky:"sticky bottom-0 z-20",fixed:"fixed inset-x-0 bottom-0 z-30"},x={xs:"min-h-10",sm:"min-h-12",md:"min-h-14",lg:"min-h-16",xl:"min-h-18"},g={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"}},h={xs:"gap-0.5",sm:"gap-0.5",md:"gap-1",lg:"gap-1",xl:"gap-1.5"};export const BottomNavigation=l.forwardRef(function({variant:a="outline",size:t="md",color:s="primary",density:r="default",elevation:o=0,value:c,defaultValue:u=null,onValueChange:h,position:y="fixed",labels:w="all",divider:N=!0,safeArea:z=!0,disabled:k=!1,label:j,render:C,className:B,style:R,children:V,..._},A){const[D,F]=l.useState(u),I=void 0!==c,M=I?c:D,P=l.useCallback(e=>{I||F(e),h?.(e)},[I,h]),S=l.useMemo(()=>({value:M??null,change:P,size:t,density:r,labels:w,disabled:k}),[M,P,t,r,w,k]),q=i("w-full min-w-0","static"===y?d[t]:"",b[a],"outline"===a&&"static"===y?"border [border-color:var(--n-line)]":"",N?"border-t [border-color:var(--n-line)]":"",v[y],z?"pb-[env(safe-area-inset-bottom)]":"",f,B);return n({render:C??e("nav",{}),ref:A,props:{"aria-label":j,className:q,style:{...m(s,o),...R},children:e(p.Provider,{value:S,children:e("div",{className:i("flex w-full items-stretch",x[t],g[r][t]),children:V})}),..._}})});export const BottomNavigationItem=l.forwardRef(function({value:n,icon:u,href:m,disabled:b=!1,className:v,children:x,onClick:g,...y},w){const N=l.useContext(p),z=b||N.disabled,k=null!==N.value&&N.value===n,j="all"===N.labels||"selected"===N.labels&&k,C=i("flex min-w-0 flex-1 flex-col items-center justify-center px-1 py-1.5",h[N.size],d[N.size],"[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]",f,"focus-visible:[outline:2px_solid_var(--n-ring)] focus-visible:[outline-offset:-2px]",z?"cursor-not-allowed text-(--neba-disabled-fg)":k?"cursor-pointer font-medium text-(--n-accent) bg-(--n-soft) hover:bg-(--n-soft-hover)":"cursor-pointer text-(--neba-muted-fg) hover:text-(--neba-fg) hover:bg-(--n-panel-hover)",v),B=t(a,{children:[s(u)?e("span",{className:i("flex shrink-0 items-center justify-center",r[N.size]),children:u}):null,s(x)?e("span",{className:j?i("max-w-full truncate leading-tight",o[N.size]):c,children:x}):null]}),R=e=>{z?e.preventDefault():(N.change(n),g?.(e))};return m?e("a",{ref:w,href:z?void 0:m,"aria-current":k?"page":void 0,"aria-disabled":z||void 0,className:C,onClick:R,...y,children:B}):e("button",{ref:w,type:"button",disabled:z,"aria-current":k?"page":void 0,className:C,onClick:R,...y,children:B})});
1
+ import{jsx as e,Fragment as t,jsxs as a}from"react/jsx-runtime";import*as n from"react";import{useRender as i}from"@base-ui/react/use-render";import{barMinHeightClasses as r,BottomNavigationContext as l}from"../../internal/bottom-navigation";import{cx as o,hasContent as s,iconSizeClasses as d,metaTextClasses as c,paddingXClasses as m,radiusClasses as u,srOnlyClasses as p,surfaceClasses as f,surfaceSlots as g,transitionClasses as b}from"../../internal/styles";const v={solid:[f,"text-(--neba-fg) bg-(--n-panel-hover)","[box-shadow:var(--n-elev),var(--neba-plate-solid)]"].join(" "),outline:[f,"text-(--neba-fg) bg-(--n-panel)","[box-shadow:var(--n-elev),var(--neba-plate-glass)]"].join(" "),text:"text-(--neba-fg) bg-transparent"},x={static:"",sticky:"sticky bottom-0 z-20",fixed:"fixed inset-x-0 bottom-0 z-30"},h={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 BottomNavigation=n.forwardRef(function({variant:t="outline",size:a="md",color:s="primary",density:d="default",elevation:c=0,value:m,defaultValue:p=null,onValueChange:f,position:y="fixed",labels:w="all",divider:N=!0,safeArea:z=!0,disabled:j=!1,label:k,render:C,className:B,style:R,children:V,..._},A){const[D,F]=n.useState(p),I=void 0!==m,M=I?m:D,P=n.useCallback(e=>{I||F(e),f?.(e)},[I,f]),S=n.useMemo(()=>({value:M??null,change:P,size:a,density:d,labels:w,disabled:j,floating:!1}),[M,P,a,d,w,j]),q=o("w-full min-w-0","static"===y?u[a]:"",v[t],"outline"===t&&"static"===y?"border [border-color:var(--n-line)]":"",N?"border-t [border-color:var(--n-line)]":"",x[y],z?"pb-[env(safe-area-inset-bottom)]":"",b,B);return i({render:C??e("nav",{}),ref:A,props:{"aria-label":k,className:q,style:{...g(s,c),...R},children:e(l.Provider,{value:S,children:e("div",{className:o("flex w-full items-stretch",r[a],h[d][a]),children:V})}),..._}})});export const BottomNavigationItem=n.forwardRef(function({value:i,icon:r,href:f,disabled:g=!1,className:v,children:x,onClick:h,...w},N){const z=n.useContext(l),j=g||z.disabled,k=null!==z.value&&z.value===i,C="all"===z.labels||"selected"===z.labels&&k,B=o("flex min-w-0 flex-col items-center justify-center py-1.5",z.floating?o("relative z-10",m[z.density][z.size],"rounded-full"):o("flex-1 px-1",u[z.size]),y[z.size],"[-webkit-tap-highlight-color:transparent] [touch-action:manipulation]",b,"focus-visible:[outline:2px_solid_var(--n-ring)] focus-visible:[outline-offset:-2px]",j?"cursor-not-allowed text-(--neba-disabled-fg)":k?z.floating?"cursor-pointer font-medium text-(--n-accent)":"cursor-pointer font-medium text-(--n-accent) bg-(--n-soft) hover:bg-(--n-soft-hover)":"cursor-pointer text-(--neba-muted-fg) hover:text-(--neba-fg) hover:bg-(--n-panel-hover)",v),R=a(t,{children:[s(r)?e("span",{className:o("flex shrink-0 items-center justify-center",d[z.size]),children:r}):null,s(x)?z.floating?e("span",{"data-drawn":C?"":void 0,className:o("grid justify-items-center","[transition-property:grid-template-rows,grid-template-columns,opacity]","[transition-duration:var(--neba-duration)]","[transition-timing-function:var(--neba-ease)]","motion-reduce:[transition-duration:0ms]",C?"[grid-template-columns:1fr] [grid-template-rows:1fr] opacity-100":"[grid-template-columns:0fr] [grid-template-rows:0fr] opacity-0"),children:e("span",{className:o("min-h-0 min-w-0 truncate leading-tight",c[z.size]),children:x})}):e("span",{"data-drawn":C?"":void 0,className:C?o("max-w-full truncate leading-tight",c[z.size]):p,children:x}):null]}),V=e=>{j?e.preventDefault():(z.change(i),h?.(e))};return f?e("a",{ref:N,"data-nav-item":"",href:j?void 0:f,"aria-current":k?"page":void 0,"aria-disabled":j||void 0,className:B,onClick:V,...w,children:R}):e("button",{ref:N,"data-nav-item":"",type:"button",disabled:j,"aria-current":k?"page":void 0,className:B,onClick:V,...w,children:R})});
@@ -0,0 +1,91 @@
1
+ import * as React from 'react';
2
+ import { useRender } from '@base-ui/react/use-render';
3
+ import type { BottomNavigationLabels, BottomNavigationValue } from '../../internal/bottom-navigation';
4
+ import type { NebaElevation, NebaPosition, NebaStyleProps } from '../../types';
5
+ /**
6
+ * Where a floating bar sits, with one value the shared vocabulary does not have.
7
+ *
8
+ * `absolute` is not a second spelling of anything in `NebaPosition` — it is the
9
+ * fourth CSS value, and it is the one that makes the bar belong to a *region*
10
+ * rather than to the window: a bar inside a Mockup's screen, inside a card,
11
+ * inside a preview. The same addition FloatingActionButton makes, for the same
12
+ * reason.
13
+ */
14
+ export type FloatingBottomNavigationPosition = NebaPosition | 'absolute';
15
+ export interface FloatingBottomNavigationProps extends NebaStyleProps, Omit<React.ComponentPropsWithoutRef<'nav'>, 'color' | 'defaultValue' | 'onChange'> {
16
+ /** The destination the reader is on. Use with `onValueChange` for a controlled bar. */
17
+ value?: BottomNavigationValue | null;
18
+ /** Which starts current, for an uncontrolled bar. */
19
+ defaultValue?: BottomNavigationValue | null;
20
+ onValueChange?: (value: BottomNavigationValue) => void;
21
+ /**
22
+ * How the bar sits in the page's scroll. `fixed` — the default — holds it
23
+ * against the bottom of the window; `sticky` holds it against the bottom of
24
+ * whatever is scrolling; `absolute` holds it against the bottom of the
25
+ * nearest positioned ancestor, which is what a bar inside a screen of its own
26
+ * wants; `static` puts it back in the flow, centred.
27
+ * @default 'fixed'
28
+ */
29
+ position?: FloatingBottomNavigationPosition;
30
+ /**
31
+ * How far the bar floats above the bottom edge — a number in pixels or any
32
+ * CSS length. This is the whole difference between this component and
33
+ * [BottomNavigation]: the gap under it is what makes the page keep going
34
+ * underneath rather than stop at a bar.
35
+ * @default 16
36
+ */
37
+ offset?: number | string;
38
+ /**
39
+ * Which names are drawn. `selected` here, against the `all` a full-width bar
40
+ * defaults to: this bar is only as wide as what is in it, so five drawn names
41
+ * would stretch it across the screen and it would stop being a lozenge.
42
+ * @default 'selected'
43
+ */
44
+ labels?: BottomNavigationLabels;
45
+ /**
46
+ * Adds `env(safe-area-inset-bottom)` to `offset`, so the bar clears a phone's
47
+ * home indicator rather than sitting on it. Unlike on BottomNavigation this
48
+ * moves the whole sheet: there is nothing under it to keep covered.
49
+ * @default true
50
+ */
51
+ safeArea?: boolean;
52
+ /**
53
+ * Drop shadow depth. `2` here, against the `0` everything else defaults to
54
+ * and against BottomNavigation's own `0`, for the reason Pill's is `2`: this
55
+ * bar is defined by not being part of the page. A lozenge floating flat over
56
+ * the content it is floating over reads as a mistake.
57
+ * @default 2
58
+ */
59
+ elevation?: NebaElevation;
60
+ /** Every destination stops answering. */
61
+ disabled?: boolean;
62
+ /** The name the bar is announced by — "Main", "Sections". */
63
+ label?: string;
64
+ /**
65
+ * Renders something other than a `<nav>`. Base UI's own escape hatch, and
66
+ * rarely what you want here: a row of destinations is navigation.
67
+ */
68
+ render?: useRender.RenderProp;
69
+ /** The BottomNavigationItems. The same item both bars take. */
70
+ children?: React.ReactNode;
71
+ }
72
+ /**
73
+ * A row of destinations floating clear of the bottom edge of the window.
74
+ *
75
+ * It is [BottomNavigation] lifted off the page: the same `<nav>`, the same
76
+ * `aria-current`, the same BottomNavigationItem children, and deliberately not
77
+ * a `role="tablist"` for the same reason — a bottom navigation changes what the
78
+ * page is, not which panel of one is showing.
79
+ *
80
+ * What differs is the shape, and everything about the shape follows from
81
+ * `offset`. Because the page keeps going underneath, the sheet is a stadium
82
+ * rather than a bar with two corners, it is only as wide as its destinations,
83
+ * it carries a shadow, and it names only the destination the reader is on —
84
+ * five drawn names would stretch it back into a bar.
85
+ *
86
+ * The stadium is the exception the house radius rule is drawn against, exactly
87
+ * as Pill is: a control is held short of the 50% that would make it a pill
88
+ * because the flat run along its edge is what reads as a sheet with the corners
89
+ * cut off. Nothing here is lying on the page.
90
+ */
91
+ export declare const FloatingBottomNavigation: React.ForwardRefExoticComponent<FloatingBottomNavigationProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as a from"react";import{useRender as n}from"@base-ui/react/use-render";import{barMinHeightClasses as r,BottomNavigationContext as o}from"../../internal/bottom-navigation";import{cx as s,surfaceClasses as l,surfaceSlots as i,transitionClasses as f}from"../../internal/styles";const u={solid:[l,"text-(--neba-fg) bg-(--n-panel-press)","[box-shadow:var(--n-elev),var(--neba-plate-solid)]"].join(" "),outline:[l,"border text-(--neba-fg) bg-(--n-panel-hover)","[border-color:var(--n-line)]","[box-shadow:var(--n-elev),var(--neba-plate-glass)]"].join(" "),text:"text-(--neba-fg) bg-transparent"},d={static:"relative mx-auto w-fit",absolute:"absolute inset-x-0 bottom-(--n-nav-offset) z-30 mx-auto w-fit",sticky:"sticky bottom-(--n-nav-offset) z-30 mx-auto w-fit",fixed:"fixed inset-x-0 bottom-(--n-nav-offset) z-40 mx-auto w-fit"},p={default:{xs:"p-1",sm:"p-1",md:"p-1.5",lg:"p-1.5",xl:"p-2"},compact:{xs:"p-0.5",sm:"p-0.5",md:"p-1",lg:"p-1",xl:"p-1"}},c={solid:`${l} bg-(--n-panel-hover) [box-shadow:var(--neba-shadow-1),var(--neba-plate-glass)]`,outline:`${l} bg-(--n-soft-hover) [box-shadow:var(--neba-shadow-1),var(--neba-plate-glass)]`,text:"bg-(--n-soft-hover)"},b={xs:"gap-0.5",sm:"gap-0.5",md:"gap-1",lg:"gap-1",xl:"gap-1.5"};export const FloatingBottomNavigation=a.forwardRef(function({variant:l="outline",size:v="md",color:m="primary",density:x="default",elevation:g=2,value:y,defaultValue:h=null,onValueChange:w,position:$="fixed",offset:z=16,labels:j="selected",safeArea:k=!0,disabled:P=!1,label:A,render:C,className:N,style:R,children:E,...L},S){const[V,W]=a.useState(h),q=void 0!==y,B=q?y:V,F=a.useCallback(e=>{q||W(e),w?.(e)},[q,w]),H=a.useMemo(()=>({value:B??null,change:F,size:v,density:x,labels:j,disabled:P,floating:!0}),[B,F,v,x,j,P]),M=a.useRef(null),O=a.useRef(null),T=a.useCallback(e=>{M.current=e,"function"==typeof S?S(e):S&&(S.current=e)},[S]),D=a.useCallback(e=>{const t=M.current,a=O.current;if(!t||!a)return;const n=t.querySelector("[data-nav-item][aria-current]");if(!n)return;const r=!e||!a.hasAttribute("data-ready");r&&a.removeAttribute("data-ready"),a.style.setProperty("--n-nav-x",`${n.offsetLeft}px`),a.style.setProperty("--n-nav-y",`${n.offsetTop}px`),a.style.setProperty("--n-nav-w",`${n.offsetWidth}px`),a.style.setProperty("--n-nav-h",`${n.offsetHeight}px`),r&&a.offsetWidth,a.setAttribute("data-ready","")},[]);a.useLayoutEffect(()=>{D(!0)},[D,B,l,v,x,j,E]),a.useEffect(()=>{const e=M.current;if(!e)return;const t=new ResizeObserver(()=>D(!1));return t.observe(e),()=>t.disconnect()},[D]);const G="number"==typeof z?`${z}px`:z,I=s("flex max-w-full min-w-0 items-center rounded-full",r[v],p[x][v],b[v],u[l],d[$],f,N);return n({render:C??e("nav",{}),ref:T,props:{"aria-label":A,className:I,style:{...i(m,g),"--n-nav-offset":k?`calc(${G} + env(safe-area-inset-bottom))`:G,...R},children:t(o.Provider,{value:H,children:[null!=B?e("span",{ref:O,"aria-hidden":"true",className:s("pointer-events-none absolute rounded-full","top-(--n-nav-y) left-(--n-nav-x) h-(--n-nav-h) w-(--n-nav-w)",c[l],"[transition-property:left,top,width,height]","[transition-timing-function:var(--neba-ease)]","[transition-duration:0ms] data-[ready]:[transition-duration:var(--neba-duration)]","motion-reduce:[transition-duration:0ms]")}):null,E]}),...L}})});
@@ -0,0 +1,2 @@
1
+ export { FloatingBottomNavigation } from './FloatingBottomNavigation';
2
+ export type { FloatingBottomNavigationPosition, FloatingBottomNavigationProps } from './FloatingBottomNavigation';
@@ -0,0 +1 @@
1
+ export{FloatingBottomNavigation}from"./FloatingBottomNavigation";
@@ -0,0 +1,126 @@
1
+ import * as React from 'react';
2
+ import type { NebaOrientation, NebaStyleProps } from '../../types';
3
+ /**
4
+ * When the scroll buttons are drawn.
5
+ *
6
+ * - `auto` — only the one that has somewhere to go, and neither of them while
7
+ * everything fits. The default: a control that cannot do anything is worse
8
+ * than no control, and a row that does not overflow is not a scroller.
9
+ * - `always` — both, from the first paint, with the one that has nowhere to go
10
+ * `disabled` rather than gone. What a row whose content arrives later wants,
11
+ * since the buttons do not appear under the pointer half a second in.
12
+ * - `none` — none at all. Dragging, the wheel and the arrow keys are still
13
+ * there; this is the strip that scrolls the way a phone scrolls.
14
+ */
15
+ export type ScrollZoneButtons = 'auto' | 'always' | 'none';
16
+ /**
17
+ * What pressing a scroll button does.
18
+ *
19
+ * - `item` — moves to the next child along, `step` of them at a time. The
20
+ * default, and the only one that lands on something rather than between two
21
+ * things.
22
+ * - `page` — moves by everything currently on screen, the way Page Down does.
23
+ * - `hold` — scrolls for as long as the button is held, at `speed` pixels a
24
+ * second. A press too short to be a hold falls back to one `item`, so the
25
+ * button is never dead to a quick tap.
26
+ */
27
+ export type ScrollZoneMode = 'item' | 'page' | 'hold';
28
+ /**
29
+ * Where the scroll buttons sit, which is also where the strip ends.
30
+ *
31
+ * - `overlay` — over the ends of the strip, which keeps every pixel of the box
32
+ * for content and lets an item pass under a button. The default, and what a
33
+ * shelf of pictures wants.
34
+ * - `inline` — beside the strip, in the layout. The scroller stops where the
35
+ * button starts, so an item is *cut off* at the button's edge rather than
36
+ * sliding beneath it: nothing is ever half-hidden behind a control, and the
37
+ * button is legible over the page rather than over whatever it landed on.
38
+ *
39
+ * The lane an `inline` button sits in is kept even while that button has
40
+ * nowhere to go, or the strip would resize under the pointer every time it
41
+ * reached an end.
42
+ */
43
+ export type ScrollZoneButtonPlacement = 'overlay' | 'inline';
44
+ export interface ScrollZoneProps extends NebaStyleProps, Omit<React.ComponentPropsWithoutRef<'div'>, 'color'> {
45
+ /**
46
+ * Which way the children run, and therefore which way the zone scrolls.
47
+ * @default 'horizontal'
48
+ */
49
+ orientation?: NebaOrientation;
50
+ /**
51
+ * How many rows a horizontal zone lays its children out in before it starts a
52
+ * new column — and how many columns a vertical one uses. `2` is the shelf
53
+ * that holds twice as much in the same width.
54
+ * @default 1
55
+ */
56
+ lines?: number;
57
+ /**
58
+ * The gap between children, on Tailwind's spacing scale: `2` is `0.5rem`, the
59
+ * same step `gap-2` is. The same ladder GridContainer's own `spacing` is on.
60
+ * @default 2
61
+ */
62
+ spacing?: number;
63
+ /** When the scroll buttons are drawn. @default 'auto' */
64
+ buttons?: ScrollZoneButtons;
65
+ /**
66
+ * Whether the buttons sit over the strip or beside it. `inline` is what to
67
+ * reach for when an item disappearing under a button reads as a bug rather
68
+ * than as depth.
69
+ * @default 'overlay'
70
+ */
71
+ buttonPlacement?: ScrollZoneButtonPlacement;
72
+ /** What pressing one does. @default 'item' */
73
+ mode?: ScrollZoneMode;
74
+ /** How many children one press moves, in `item` mode. @default 1 */
75
+ step?: number;
76
+ /** How fast a held button scrolls, in pixels a second. @default 900 */
77
+ speed?: number;
78
+ /**
79
+ * Snaps the nearest child to the leading edge when the scrolling stops —
80
+ * dragging and the wheel included, not only the buttons.
81
+ * @default false
82
+ */
83
+ snap?: boolean;
84
+ /**
85
+ * Lets a mouse or a pen drag the strip along, the way a finger already does.
86
+ * Touch is left to the browser, whose own scrolling is better than anything a
87
+ * handler can imitate: momentum, rubber-banding and the scrollbar all come
88
+ * with it.
89
+ * @default true
90
+ */
91
+ drag?: boolean;
92
+ /** Shows the native scrollbar. @default false */
93
+ scrollbar?: boolean;
94
+ /**
95
+ * Which language the buttons name themselves in — a BCP 47 tag such as `ko`,
96
+ * `pt-BR` or `zh-Hant`. Unsupported tags fall back to English.
97
+ */
98
+ locale?: string;
99
+ /** What the scrollable region is called — "Categories", "Recent files". */
100
+ label?: string;
101
+ /** Overrides the buttons' own names. */
102
+ previousLabel?: string;
103
+ nextLabel?: string;
104
+ /** What is being laid out. Every top-level child is one item of the strip. */
105
+ children?: React.ReactNode;
106
+ }
107
+ /**
108
+ * A strip of anything, laid out in one direction and scrolled in it.
109
+ *
110
+ * The mechanism is an ordinary scroll container, and everything the component
111
+ * offers is a way of driving one. Swiping on a phone, two-finger dragging on a
112
+ * trackpad, the wheel, the arrow keys and the scrollbar are the browser's own
113
+ * and are never intercepted; what is added on top is a pair of buttons for the
114
+ * pointer that has neither a wheel nor a finger, and a mouse drag for the strip
115
+ * that reads as something to pull rather than something to page.
116
+ *
117
+ * Nothing is transformed. A translated track would have to argue for an
118
+ * exception to the house rule; a scroll offset does not, and it is also what
119
+ * makes the strip run the other way under RTL without being told, keeps the
120
+ * scrollbar honest, and lets the browser scroll a focused child into view.
121
+ *
122
+ * `lines` is what separates this from a Carousel: a Carousel is one thing at a
123
+ * time and knows which one, a ScrollZone is a shelf that happens to be longer
124
+ * than the room it is in.
125
+ */
126
+ export declare const ScrollZone: React.ForwardRefExoticComponent<ScrollZoneProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as n from"react";import{IconButton as r}from"../icon-button/IconButton";import{spacingValue as o}from"../../internal/grid";import{useMessages as i}from"../../internal/i18n";import{ChevronIcon as s}from"../../internal/icons";import{cx as l}from"../../internal/styles";const a={xs:"p-1",sm:"p-1.5",md:"p-2",lg:"p-3",xl:"p-4"},c={xs:"gap-1",sm:"gap-1.5",md:"gap-2",lg:"gap-3",xl:"gap-4"};export const ScrollZone=n.forwardRef(function({orientation:u="horizontal",lines:d=1,spacing:m=2,buttons:p="auto",buttonPlacement:f="overlay",mode:v="item",step:b=1,speed:w=900,snap:g=!1,drag:h=!0,scrollbar:y=!1,variant:x="solid",size:k="md",color:L="primary",density:E="default",locale:C,label:R,previousLabel:P,nextLabel:M,className:N,style:T,children:A,...z},j){const D=i(C),S="horizontal"===u,B=Math.max(1,Math.round(d)),F=Math.max(1,Math.round(b)),H=n.useRef(null),I=n.useRef(null),[W,$]=n.useState({back:!1,forward:!1}),q=n.useCallback(()=>{const e=H.current;if(!e)return;const t=S?e.clientWidth:e.clientHeight,n=S?e.scrollWidth:e.scrollHeight,r=Math.abs(S?e.scrollLeft:e.scrollTop);$(e=>{const o=r>1,i=n-t-r>1;return e.back===o&&e.forward===i?e:{back:o,forward:i}})},[S]);n.useEffect(()=>{const e=H.current,t=I.current;if(!e)return;if(q(),"undefined"==typeof ResizeObserver)return;const n=new ResizeObserver(q);return n.observe(e),t&&n.observe(t),()=>n.disconnect()},[q]);const O=n.useCallback(()=>{const e=H.current;return e&&S&&"rtl"===getComputedStyle(e).direction?-1:1},[S]),X=n.useCallback((e,t)=>{const n=H.current;if(!n)return;const r=t?"undefined"!=typeof window&&window.matchMedia?.("(prefers-reduced-motion: reduce)").matches?"auto":"smooth":"auto";n.scrollBy(S?{left:e,behavior:r}:{top:e,behavior:r})},[S]),Y=n.useCallback(()=>{const e=H.current,t=I.current;if(!e||!t)return[];const n=S&&"rtl"===getComputedStyle(e).direction,r=S?n?"right":"left":"top",o=n?-1:1,i=e.getBoundingClientRect()[r],s=[];for(const e of Array.from(t.children)){const t=Math.round((e.getBoundingClientRect()[r]-i)*o);s.includes(t)||s.push(t)}return s.sort((e,t)=>e-t)},[S]),_=n.useCallback((e,t=v)=>{const n=H.current;if(!n)return;const r=O(),o=S?n.clientWidth:n.clientHeight;if("page"===t)return void X(o*(e?1:-1)*r,!0);const i=Y(),s=i.filter(e=>e>1),l=i.filter(e=>e<-1),a=e?s[F-1]:l[l.length-F];X(void 0!==a?a*r:o*(e?1:-1)*r,!0)},[O,S,Y,F,v,X]),K=n.useRef(null);n.useEffect(()=>()=>K.current?.(),[]);const V=n.useCallback((e,t)=>{if(K.current)return;const n=O(),r=performance.now();let o=r,i=0;const s=t=>{const r=t-o;o=t,X(w*r/1e3*(e?1:-1)*n,!1),i=requestAnimationFrame(s)};i=requestAnimationFrame(s);const l=()=>{cancelAnimationFrame(i),K.current=null,window.removeEventListener(t,l),window.removeEventListener("pointercancel",l),window.removeEventListener("blur",l),performance.now()-r<140&&_(e,"item")};K.current=l,window.addEventListener(t,l),window.addEventListener("pointercancel",l),window.addEventListener("blur",l)},[_,O,X,w]),Z=n.useRef(null);function G(e){return"hold"!==v?{onClick:()=>_(e)}:{onPointerDown:t=>{0===t.button&&V(e,"pointerup")},onKeyDown:t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),V(e,"keyup"))}}}n.useEffect(()=>()=>Z.current?.(),[]);const J="none"!==p&&("always"===p||W.back||W.forward),Q="inline"===f;function U(t){const n=t?W.forward:W.back;if("auto"===p&&!n&&!Q)return e("span",{});const o=S?t?"-rotate-90 rtl:rotate-90":"rotate-90 rtl:-rotate-90":t?"":"rotate-180",i="auto"===p&&!n,a=e(r,{variant:x,size:k,color:L,density:E,elevation:1,label:t?M??D.scroll.next:P??D.scroll.previous,disabled:!n,className:"pointer-events-auto",icon:e("span",{className:l("flex items-center",o),children:e(s,{})}),...G(t)});return Q?e("span",{className:l("flex shrink-0 items-center justify-center",i?"invisible":""),inert:i,children:a}):a}return t("div",{ref:j,className:l("relative flex min-w-0",Q&&S?"flex-row items-stretch":"flex-col",Q?c[k]:"",N),style:{"--n-ring":`var(--neba-${L}-ring)`,...T},...z,children:[J&&Q?U(!1):null,e("div",{ref:H,tabIndex:0,role:R?"group":void 0,"aria-label":R,className:l("min-h-0 min-w-0 grow",S?"overflow-x-auto overflow-y-hidden":"overflow-y-auto overflow-x-hidden",g?S?"snap-x snap-mandatory":"snap-y snap-mandatory":"",y?"":"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",h&&(W.back||W.forward)?"cursor-grab data-[dragging]:cursor-grabbing":"","focus-visible:[outline:2px_solid_var(--n-ring)] focus-visible:[outline-offset:-2px]"),onScroll:q,onPointerDown:function(e){if(!h||"touch"===e.pointerType||0!==e.button)return;Z.current?.();const t=e.currentTarget,n=e.clientX,r=e.clientY,o=t.scrollLeft,i=t.scrollTop;let s=!1;const l=document.body.style.getPropertyValue("-webkit-user-select"),a=e=>{const l=e.clientX-n,a=e.clientY-r;if(!s){if(Math.abs(S?l:a)<4)return;s=!0,t.setPointerCapture(e.pointerId),t.dataset.dragging="true",document.body.style.setProperty("-webkit-user-select","none")}S?t.scrollLeft=o-l:t.scrollTop=i-a},c=()=>{Z.current=null,t.removeEventListener("pointermove",a),t.removeEventListener("pointerup",u),t.removeEventListener("pointercancel",c),delete t.dataset.dragging,l?document.body.style.setProperty("-webkit-user-select",l):document.body.style.removeProperty("-webkit-user-select")},u=()=>{const e=s;if(c(),!e)return;const n=e=>{e.stopPropagation(),e.preventDefault()};t.addEventListener("click",n,{capture:!0,once:!0}),window.setTimeout(()=>t.removeEventListener("click",n,!0),0)};Z.current=c,t.addEventListener("pointermove",a),t.addEventListener("pointerup",u),t.addEventListener("pointercancel",c)},children:e("div",{ref:I,className:l(g?"[&>*]:snap-start":""),style:{display:"grid",gap:o(m),gridAutoFlow:S?"column":"row",...S?{width:"max-content",gridTemplateRows:`repeat(${B}, auto)`,gridAutoColumns:"max-content"}:{gridTemplateColumns:`repeat(${B}, minmax(0, 1fr))`,gridAutoRows:"max-content"}},children:A})}),J&&Q?U(!0):null,J&&!Q?t("div",{className:l("pointer-events-none absolute inset-0 flex justify-between",S?"items-center":"flex-col items-center",a[k]),children:[U(!1),U(!0)]}):null]})});
@@ -0,0 +1,2 @@
1
+ export { ScrollZone } from './ScrollZone';
2
+ export type { ScrollZoneButtonPlacement, ScrollZoneButtons, ScrollZoneMode, ScrollZoneProps } from './ScrollZone';
@@ -0,0 +1 @@
1
+ export{ScrollZone}from"./ScrollZone";
@@ -0,0 +1,178 @@
1
+ import * as React from 'react';
2
+ import { useRender } from '@base-ui/react/use-render';
3
+ import type { NebaWindowControl, NebaWindowOffset, NebaWindowOs } from '../../internal/window';
4
+ import type { NebaColor, NebaElevation, NebaSize } from '../../types';
5
+ export type { NebaWindowControl, NebaWindowOffset, NebaWindowOs } from '../../internal/window';
6
+ /** The three CSS positions a window can be laid out with. */
7
+ export type WindowPanePosition = 'static' | 'absolute' | 'fixed';
8
+ /** What a resize reports: the window's size in pixels. */
9
+ export interface WindowPaneSize {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface WindowPaneProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'color' | 'title' | 'onResize'> {
14
+ /**
15
+ * Whose window this is a picture of. Decides where the controls sit, how they
16
+ * are drawn, how tall the title bar is and how its corners are cut.
17
+ * @default 'macos'
18
+ */
19
+ os?: NebaWindowOs;
20
+ /** The window's name, in the title bar. Also what names the window itself. */
21
+ title?: React.ReactNode;
22
+ /** A glyph beside the title — the app's mark. */
23
+ icon?: React.ReactNode;
24
+ /** Anything else the title bar carries, set beside the controls. */
25
+ actions?: React.ReactNode;
26
+ /**
27
+ * Which of the three buttons the title bar has. `true` is all of them,
28
+ * `false` is none, and an array is exactly the ones named — a window that can
29
+ * be closed but not minimized is `['close']`.
30
+ *
31
+ * The order is the system's rather than the array's: macOS puts close first
32
+ * and Windows puts it last, and that is not something a caller should have to
33
+ * remember.
34
+ * @default true
35
+ */
36
+ controls?: boolean | readonly NebaWindowControl[];
37
+ /**
38
+ * The scale of the chrome — the title bar's height, its buttons and its type.
39
+ * It does not touch the content, which is the caller's and is laid out at its
40
+ * own scale, exactly as it would be on a desktop where the title bar does not
41
+ * grow with the document.
42
+ * @default 'md'
43
+ */
44
+ size?: NebaSize;
45
+ /** The colour family the focus rings and an `accent` title bar take. @default 'primary' */
46
+ color?: NebaColor;
47
+ /**
48
+ * Dyes the title bar with the colour family, the way Windows offers to. Off,
49
+ * the bar is the neutral shade the system draws by default.
50
+ * @default false
51
+ */
52
+ accent?: boolean;
53
+ /**
54
+ * How much of what is behind the window shows through its chrome, from `0` to
55
+ * `1`. It applies to the title bar, the body's own fill and the border —
56
+ * never to the content on it, which stays exactly as legible as it was.
57
+ *
58
+ * Anything above `0` also turns the acrylic on, so the page underneath is
59
+ * blurred rather than merely visible. `1` is a window that is nothing but its
60
+ * edge and its chrome.
61
+ * @default 0
62
+ */
63
+ transparency?: number;
64
+ /**
65
+ * Whether this is the window in front.
66
+ *
67
+ * Left out, the window works it out for itself: it is in front until another
68
+ * WindowPane on the page is pressed or takes the focus, exactly as windows on
69
+ * a desktop behave. A click on the page *around* the windows changes nothing
70
+ * — a paragraph is not a desktop.
71
+ *
72
+ * Pass it to drive that yourself, which is what a caller keeping its own
73
+ * z-order wants.
74
+ *
75
+ * What being in front looks like is the system's: coloured traffic lights on
76
+ * macOS against grey ones, an accent title bar and an accent border on
77
+ * Windows 10, a tinted header bar on GNOME — and, on all four, a window one
78
+ * step further off the page than the ones behind it.
79
+ */
80
+ active?: boolean;
81
+ /**
82
+ * The shadow around the window. `2` here, against the `0` everything else
83
+ * defaults to, for the reason Pill's is `2`: a window is by definition not
84
+ * part of the page it is on.
85
+ * @default 2
86
+ */
87
+ elevation?: NebaElevation;
88
+ /**
89
+ * How the window is laid out. `static` leaves it in the flow (as a
90
+ * relatively positioned box, so `offset` can move it without disturbing
91
+ * anything around it); `absolute` pins it inside the nearest positioned
92
+ * ancestor, which is what a desktop full of windows wants; `fixed` pins it to
93
+ * the viewport.
94
+ * @default 'static'
95
+ */
96
+ position?: WindowPanePosition;
97
+ /** Lets the title bar be dragged. @default false */
98
+ draggable?: boolean;
99
+ /** Lets the edges and corners be dragged. @default false */
100
+ resizable?: boolean;
101
+ /** The window's width — a number in pixels or any CSS length. */
102
+ width?: number | string;
103
+ /** And its height. Left out, the window is as tall as what is in it. */
104
+ height?: number | string;
105
+ /** How small it may be dragged, in pixels. @default 180 */
106
+ minWidth?: number;
107
+ /** The same downward. Defaults to the title bar's own height. */
108
+ minHeight?: number;
109
+ /** How far it has been dragged from where the layout put it. */
110
+ offset?: NebaWindowOffset;
111
+ /** Where an uncontrolled window starts. @default { x: 0, y: 0 } */
112
+ defaultOffset?: NebaWindowOffset;
113
+ onOffsetChange?: (offset: NebaWindowOffset) => void;
114
+ /** Fires with the window's size, in pixels, while an edge is dragged. */
115
+ onResize?: (size: WindowPaneSize) => void;
116
+ /** Whether the window is on screen at all. Closing it renders nothing. */
117
+ open?: boolean;
118
+ /** @default true */
119
+ defaultOpen?: boolean;
120
+ onOpenChange?: (open: boolean) => void;
121
+ /**
122
+ * Whether the window is rolled up to its title bar. This is what minimizing
123
+ * means for a window that is part of a page rather than of a desktop: there
124
+ * is no dock for it to go to, so it stays where it is with nothing under the
125
+ * bar.
126
+ */
127
+ minimized?: boolean;
128
+ /** @default false */
129
+ defaultMinimized?: boolean;
130
+ onMinimizedChange?: (minimized: boolean) => void;
131
+ /**
132
+ * Whether the window fills whatever is holding it. Its corners go square
133
+ * while it does, as they do on every system.
134
+ */
135
+ maximized?: boolean;
136
+ /** @default false */
137
+ defaultMaximized?: boolean;
138
+ onMaximizedChange?: (maximized: boolean) => void;
139
+ /** Whether content taller than the window scrolls. @default true */
140
+ scroll?: boolean;
141
+ /**
142
+ * Which language the title bar's buttons name themselves in — a BCP 47 tag
143
+ * such as `ko`, `pt-BR` or `zh-Hant`. Unsupported tags fall back to English.
144
+ */
145
+ locale?: string;
146
+ /** Overrides the buttons' own names. */
147
+ minimizeLabel?: string;
148
+ maximizeLabel?: string;
149
+ restoreLabel?: string;
150
+ closeLabel?: string;
151
+ resizeLabel?: string;
152
+ /** Renders something other than a `<div>`: `render={<section />}`. */
153
+ render?: useRender.RenderProp;
154
+ /** What is in the window. */
155
+ children?: React.ReactNode;
156
+ }
157
+ /**
158
+ * A window, drawn the way one of four systems draws it, with anything at all
159
+ * inside it.
160
+ *
161
+ * It is not a real window and does not pretend to be one: there is no desktop,
162
+ * no z-order and no dock. What it is is a *frame that behaves* — the title bar
163
+ * drags, the corners resize, the three buttons are real buttons with real names
164
+ * — so a screenshot of an app, a demo of a feature or a piece of a landing page
165
+ * can be shown as the thing it will be rather than as a picture of it.
166
+ *
167
+ * Nothing here is transformed. A dragged window moves on `left` and `top` and a
168
+ * resized one changes `width` and `height`, which is what keeps the text inside
169
+ * it at whole pixels through both gestures — a `translate()` would resample
170
+ * every glyph in the window for the length of the drag, which is exactly what
171
+ * the house rule against transforming a surface exists to prevent.
172
+ *
173
+ * `minimize` rolls the window up to its title bar rather than sending it
174
+ * anywhere, because a page has nowhere to send it to. `maximize` fills whatever
175
+ * is holding the window, which is the container for `position="absolute"` and
176
+ * the viewport for `fixed`.
177
+ */
178
+ export declare const WindowPane: React.ForwardRefExoticComponent<WindowPaneProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as t,Fragment as n}from"react/jsx-runtime";import*as i from"react";import{useRender as o}from"@base-ui/react/use-render";import{useMessages as r}from"../../internal/i18n";import{cx as s,hasContent as a,iconClasses as d,surfaceClasses as l}from"../../internal/styles";import{WindowControls as c,orderControls as u,windowChrome as m,windowMetrics as h,windowSlots as w}from"../../internal/window";function g(e,t,n){const[o,r]=i.useState(t),s=void 0!==e;return[s?e:o,i.useCallback(e=>{s||r(e),n?.(e)},[s,n])]}const f=[{edge:"n",className:"inset-x-3 top-0 h-1.5 cursor-ns-resize"},{edge:"s",className:"inset-x-3 bottom-0 h-1.5 cursor-ns-resize"},{edge:"w",className:"inset-y-3 left-0 w-1.5 cursor-ew-resize"},{edge:"e",className:"inset-y-3 right-0 w-1.5 cursor-ew-resize"},{edge:"nw",className:"top-0 left-0 size-3 cursor-nwse-resize"},{edge:"ne",className:"top-0 right-0 size-3 cursor-nesw-resize"},{edge:"sw",className:"bottom-0 left-0 size-3 cursor-nesw-resize"},{edge:"se",className:"right-0 bottom-0 size-3 cursor-nwse-resize"}];export const WindowPane=i.forwardRef(function({os:p="macos",title:b,icon:v,actions:x,controls:y=!0,size:z="md",color:E="primary",accent:N=!1,transparency:k=0,active:C,elevation:L=2,position:M="static",draggable:S=!1,resizable:P=!1,width:R,height:I,minWidth:B=180,minHeight:D,offset:A,defaultOffset:T,onOffsetChange:H,onResize:W,open:j,defaultOpen:O=!0,onOpenChange:X,minimized:$,defaultMinimized:F=!1,onMinimizedChange:Y,maximized:_,defaultMaximized:q=!1,onMaximizedChange:K,scroll:U=!0,locale:V,minimizeLabel:G,maximizeLabel:J,restoreLabel:Q,closeLabel:Z,resizeLabel:ee,render:te,className:ne,style:ie,children:oe,...re},se){const ae=r(V),de=m(p),le=h(p,z),ce=i.useId(),[ue,me]=g(j,O,X),[he,we]=g($,F,Y),[ge,fe]=g(_,q,K),[pe,be]=g(A,T??{x:0,y:0},H),[ve,xe]=i.useState(null),[ye,ze]=i.useState(!0),Ee=C??ye,[Ne,ke]=i.useState(null),[Ce,Le]=i.useState(null),[Me,Se]=i.useState(!1),[Pe,Re]=i.useState(!1),[Ie,Be]=i.useState(ue);Ie!==ue&&(Be(ue),Re(!ue)),i.useEffect(()=>{if(!Pe)return;const e=window.setTimeout(()=>Re(!1),260);return()=>window.clearTimeout(e)},[Pe]);const De=i.useRef(null),Ae=i.useCallback(e=>{De.current=e,"function"==typeof se?se(e):se&&(se.current=e)},[se]);i.useEffect(()=>{if(void 0!==C)return;const e=De.current;if(!e)return;const t=t=>{const n=t.target;if(!(n instanceof Node))return;if(e.contains(n))return void ze(!0);const i=n instanceof Element?n:n.parentElement;i?.closest(".neba-window")&&ze(!1)};return document.addEventListener("pointerdown",t,!0),document.addEventListener("focusin",t,!0),()=>{document.removeEventListener("pointerdown",t,!0),document.removeEventListener("focusin",t,!0)}},[C]);const Te=i.useRef(null);function He(e,t){if(0!==e.button)return;Te.current?.();const n=e.currentTarget;n.setPointerCapture(e.pointerId),n.dataset.dragging="true",Se(!0);const i=e.clientX,o=e.clientY,r=document.body.style.getPropertyValue("-webkit-user-select");document.body.style.setProperty("-webkit-user-select","none");const s=e=>{t(e.clientX-i,e.clientY-o)},a=()=>{Te.current=null,Se(!1),n.removeEventListener("pointermove",s),n.removeEventListener("pointerup",a),n.removeEventListener("pointercancel",a),delete n.dataset.dragging,r?document.body.style.setProperty("-webkit-user-select",r):document.body.style.removeProperty("-webkit-user-select")};Te.current=a,n.addEventListener("pointermove",s),n.addEventListener("pointerup",a),n.addEventListener("pointercancel",a)}i.useEffect(()=>()=>Te.current?.(),[]);const We={width:Math.max(0,B),height:Math.max(le.bar,D??le.bar)};function je(e){xe(e),W?.(e)}const Oe=u(p,!0===y?["minimize","maximize","close"]:!1===y?[]:y),Xe=Oe.includes("maximize");i.useEffect(()=>{if(he||null===Ce)return;const e=window.setTimeout(()=>Le(null),400);return()=>window.clearTimeout(e)},[he,Ce]);const $e={minimize:G??ae.window.minimize,maximize:J??ae.window.maximize,restore:Q??ae.window.restore,close:Z??ae.action.close},Fe=e(c,{os:p,metrics:le,controls:Oe,maximized:ge,active:Ee,labels:$e,onCommand:function(e){"close"===e?me(!1):"minimize"===e?function(e){const t=De.current,n=t?.firstElementChild;if(t&&n instanceof HTMLElement&&ke(n.offsetHeight+(t.offsetHeight-t.clientHeight)),e&&void 0===(ve?.height??I)&&t)return Le(t.getBoundingClientRect().height),void requestAnimationFrame(()=>we(!0));we(e)}(!he):fe(!ge)}}),Ye=a(b)?e("span",{id:ce,className:"min-w-0 truncate",children:b}):null,_e=a(v)?e("span",{className:"flex shrink-0 items-center",children:v}):null,qe=t("div",{className:s("relative flex shrink-0 items-center select-none",d,S&&!ge?"cursor-grab active:cursor-grabbing":""),style:{height:le.bar,textShadow:"var(--n-window-bar-shadow)",fontWeight:le.weight,paddingInlineStart:le.padX,paddingInlineEnd:le.padEnd,gap:Math.round(.7*le.title),fontSize:le.title,backgroundColor:"var(--n-window-bar)",backgroundImage:"var(--n-window-bar-image)",color:"var(--n-window-bar-fg)",borderBlockEnd:de.rule?"1px solid var(--n-window-line)":void 0},onPointerDown:function(e){if(!S||ge)return;const t={...pe};He(e,(e,n)=>be({x:t.x+e,y:t.y+n}))},onDoubleClick:Xe?()=>fe(!ge):void 0,children:["start"===de.controlsSide?Fe:null,"center"===de.titleAlign?e("span",{"aria-hidden":"true",className:"pointer-events-none absolute inset-x-0 flex items-center justify-center",style:{paddingInline:le.controlsWidth(Oe.length)+2*le.padX},children:t("span",{className:"flex min-w-0 items-center",style:{gap:Math.round(.5*le.title)},children:[_e,Ye]})}):t(n,{children:[_e,Ye]}),e("span",{className:"flex-1"}),a(x)?e("span",{className:"flex shrink-0 items-center",style:{gap:Math.round(.5*le.title)},onPointerDown:e=>e.stopPropagation(),children:x}):null,"end"===de.controlsSide?Fe:null]}),Ke=ge?{left:0,top:0,width:"100%",height:"100%"}:{left:pe.x,top:pe.y,width:ve?.width??R,height:he?Ne??le.bar:ve?.height??I??Ce??void 0},Ue=o({render:te,ref:Ae,props:{role:"group","aria-labelledby":Ye?ce:void 0,className:s("neba-window relative flex min-w-0 flex-col overflow-hidden",k>0?l:"",de.glass&&0===k?"[backdrop-filter:var(--neba-blur)]":"","[transition-property:left,top,width,height,opacity,box-shadow,background-color,border-color,border-radius]","[transition-duration:var(--neba-duration-window)]","[transition-timing-function:var(--neba-ease)]","motion-reduce:[transition-duration:0ms]","data-[gesture]:[transition-property:none]",ne),"data-gesture":Me?"":void 0,inert:!ue||void 0,style:{opacity:ue?void 0:0,...w({os:p,color:E,accent:N,transparency:k,active:Ee,elevation:L}),position:"static"===M?"relative":M,...Ke,borderRadius:ge?0:`${le.radius}px ${le.radius}px ${le.radiusBottom}px ${le.radiusBottom}px`,borderStyle:"solid",borderWidth:le.frame,borderColor:"var(--n-window-line)",backgroundColor:"var(--n-window-band)",boxShadow:"var(--n-window-shadow), var(--neba-plate-glass)",...ie},children:t(n,{children:[qe,e("div",{className:s("min-h-0 flex-1",U?"overflow-auto":"overflow-hidden"),style:{backgroundColor:"var(--n-window-body)",marginInline:le.band.side,marginBottom:le.band.bottom,boxShadow:le.band.side>0?"inset 0 0 0 1px rgb(0 0 0 / 0.12)":void 0},inert:he,children:oe}),!P||ge||he?null:f.map(t=>{const n="se"===t.edge;return e("div",{role:n?"button":void 0,tabIndex:n?0:void 0,"aria-label":n?ee??ae.window.resize:void 0,"aria-hidden":n?void 0:"true",className:s("absolute z-10 touch-none",t.className,n?"focus-visible:[outline:2px_solid_var(--n-ring)] focus-visible:[outline-offset:-2px]":""),onPointerDown:e=>function(e,t){const n=De.current;if(!n||ge)return;const i=n.getBoundingClientRect(),o={width:i.width,height:i.height,x:pe.x,y:pe.y},r=e.includes("e"),s=e.includes("w"),a=e.includes("n"),d=e.includes("s");He(t,(e,t)=>{let{width:n,height:i}=o,{x:l,y:c}=o;r&&(n=Math.max(We.width,o.width+e)),d&&(i=Math.max(We.height,o.height+t)),s&&(n=Math.max(We.width,o.width-e),l=o.x+(o.width-n)),a&&(i=Math.max(We.height,o.height-t),c=o.y+(o.height-i)),je({width:n,height:i}),(s||a)&&be({x:l,y:c})})}(t.edge,e),onKeyDown:n?e=>{const t={ArrowRight:[16,0],ArrowLeft:[-16,0],ArrowDown:[0,16],ArrowUp:[0,-16]}[e.key];t&&(e.preventDefault(),function(e,t){const n=De.current;if(!n)return;const i=n.getBoundingClientRect();je({width:Math.max(We.width,i.width+e),height:Math.max(We.height,i.height+t)})}(t[0],t[1]))}:void 0},t.edge)})]}),...re}});return ue||Pe?Ue:null});
@@ -0,0 +1,2 @@
1
+ export { WindowPane } from './WindowPane';
2
+ export type { NebaWindowControl, NebaWindowOffset, NebaWindowOs, WindowPanePosition, WindowPaneProps, WindowPaneSize } from './WindowPane';
@@ -0,0 +1 @@
1
+ export{WindowPane}from"./WindowPane";
package/dist/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export * from './components/empty';
43
43
  export * from './components/file-picker';
44
44
  export * from './components/grid';
45
45
  export * from './components/floating-action-button';
46
+ export * from './components/floating-bottom-navigation';
46
47
  export * from './components/grid-container';
47
48
  export * from './components/heatmap-chart';
48
49
  export * from './components/highlight';
@@ -66,6 +67,7 @@ export * from './components/progress-linear';
66
67
  export * from './components/radio-group';
67
68
  export * from './components/rating';
68
69
  export * from './components/scatter-chart';
70
+ export * from './components/scroll-zone';
69
71
  export * from './components/segmented-button';
70
72
  export * from './components/select';
71
73
  export * from './components/shortcut';
@@ -87,3 +89,4 @@ export * from './components/toolbar';
87
89
  export * from './components/tooltip';
88
90
  export * from './components/tree-view';
89
91
  export * from './components/typography';
92
+ export * from './components/window-pane';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./types";export*from"./components/accordion";export*from"./components/alert";export*from"./components/animate-appear";export*from"./components/animate-blink";export*from"./components/animate-fade";export*from"./components/animate-grow";export*from"./components/animate-headline";export*from"./components/animate-lighting";export*from"./components/animate-marquee";export*from"./components/animate-rotate";export*from"./components/animate-slide";export*from"./components/animate-typing";export*from"./components/animate-zoom";export*from"./components/area-chart";export*from"./components/aspect-ratio";export*from"./components/avatar";export*from"./components/badge";export*from"./components/bar-chart";export*from"./components/blockquote";export*from"./components/bottom-navigation";export*from"./components/box";export*from"./components/breadcrumb";export*from"./components/button";export*from"./components/button-group";export*from"./components/card";export*from"./components/carousel";export*from"./components/chat-bubble";export*from"./components/checkbox";export*from"./components/chip";export*from"./components/collapsible";export*from"./components/color-picker";export*from"./components/combobox";export*from"./components/container";export*from"./components/data-table";export*from"./components/date-picker";export*from"./components/date-range-picker";export*from"./components/date-time-picker";export*from"./components/dialog";export*from"./components/divider";export*from"./components/drawer";export*from"./components/empty";export*from"./components/file-picker";export*from"./components/grid";export*from"./components/floating-action-button";export*from"./components/grid-container";export*from"./components/heatmap-chart";export*from"./components/highlight";export*from"./components/icon";export*from"./components/icon-button";export*from"./components/line-chart";export*from"./components/list";export*from"./components/menu";export*from"./components/mockup";export*from"./components/number-field";export*from"./components/otp-field";export*from"./components/overlay";export*from"./components/pagination";export*from"./components/panes";export*from"./components/pie-chart";export*from"./components/pill";export*from"./components/popover";export*from"./components/progress-box";export*from"./components/progress-circular";export*from"./components/progress-linear";export*from"./components/radio-group";export*from"./components/rating";export*from"./components/scatter-chart";export*from"./components/segmented-button";export*from"./components/select";export*from"./components/shortcut";export*from"./components/skeleton";export*from"./components/slider";export*from"./components/sparkline";export*from"./components/spoiler";export*from"./components/statistic";export*from"./components/switch";export*from"./components/table";export*from"./components/tabs";export*from"./components/text-field";export*from"./components/text-link";export*from"./components/time-picker";export*from"./components/timeline";export*from"./components/timeline-chart";export*from"./components/toast";export*from"./components/toolbar";export*from"./components/tooltip";export*from"./components/tree-view";export*from"./components/typography";
1
+ export*from"./types";export*from"./components/accordion";export*from"./components/alert";export*from"./components/animate-appear";export*from"./components/animate-blink";export*from"./components/animate-fade";export*from"./components/animate-grow";export*from"./components/animate-headline";export*from"./components/animate-lighting";export*from"./components/animate-marquee";export*from"./components/animate-rotate";export*from"./components/animate-slide";export*from"./components/animate-typing";export*from"./components/animate-zoom";export*from"./components/area-chart";export*from"./components/aspect-ratio";export*from"./components/avatar";export*from"./components/badge";export*from"./components/bar-chart";export*from"./components/blockquote";export*from"./components/bottom-navigation";export*from"./components/box";export*from"./components/breadcrumb";export*from"./components/button";export*from"./components/button-group";export*from"./components/card";export*from"./components/carousel";export*from"./components/chat-bubble";export*from"./components/checkbox";export*from"./components/chip";export*from"./components/collapsible";export*from"./components/color-picker";export*from"./components/combobox";export*from"./components/container";export*from"./components/data-table";export*from"./components/date-picker";export*from"./components/date-range-picker";export*from"./components/date-time-picker";export*from"./components/dialog";export*from"./components/divider";export*from"./components/drawer";export*from"./components/empty";export*from"./components/file-picker";export*from"./components/grid";export*from"./components/floating-action-button";export*from"./components/floating-bottom-navigation";export*from"./components/grid-container";export*from"./components/heatmap-chart";export*from"./components/highlight";export*from"./components/icon";export*from"./components/icon-button";export*from"./components/line-chart";export*from"./components/list";export*from"./components/menu";export*from"./components/mockup";export*from"./components/number-field";export*from"./components/otp-field";export*from"./components/overlay";export*from"./components/pagination";export*from"./components/panes";export*from"./components/pie-chart";export*from"./components/pill";export*from"./components/popover";export*from"./components/progress-box";export*from"./components/progress-circular";export*from"./components/progress-linear";export*from"./components/radio-group";export*from"./components/rating";export*from"./components/scatter-chart";export*from"./components/scroll-zone";export*from"./components/segmented-button";export*from"./components/select";export*from"./components/shortcut";export*from"./components/skeleton";export*from"./components/slider";export*from"./components/sparkline";export*from"./components/spoiler";export*from"./components/statistic";export*from"./components/switch";export*from"./components/table";export*from"./components/tabs";export*from"./components/text-field";export*from"./components/text-link";export*from"./components/time-picker";export*from"./components/timeline";export*from"./components/timeline-chart";export*from"./components/toast";export*from"./components/toolbar";export*from"./components/tooltip";export*from"./components/tree-view";export*from"./components/typography";export*from"./components/window-pane";