@nordmanai/nif 0.2.14 → 0.2.16

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.
@@ -70,9 +70,17 @@ export type NmSidebarTriggerProps = Omit<ComponentProps<'button'>, 'aria-label'>
70
70
  icon?: NmIconId;
71
71
  /** On by default: a tooltip with the name and the ⌘B shortcut. */
72
72
  tooltip?: boolean;
73
+ /**
74
+ * Keeps the toggle at one spot of the provider whether the sidebar is open or closed: it is taken out of
75
+ * the flow and anchored at the top start corner, above the sidebar. The header leaves room for it, and
76
+ * `NmSidebarTriggerSpace` holds its place in a bar while the sidebar is closed.
77
+ */
78
+ anchored?: boolean;
73
79
  };
74
80
  /** The button that opens and closes the sidebar. In the main area's bar, or in the sidebar's own header. Resting on it peeks the sidebar. */
75
- export declare function NmSidebarTrigger({ className, label, icon, tooltip, onClick, onPointerEnter, onPointerLeave, ...props }: NmSidebarTriggerProps): ReactNode;
81
+ export declare function NmSidebarTrigger({ className, label, icon, tooltip, anchored, onClick, onPointerEnter, onPointerLeave, ...props }: NmSidebarTriggerProps): ReactNode;
82
+ /** Holds the anchored trigger's place in a bar while the sidebar is closed, so the bar's content starts beside it. */
83
+ export declare function NmSidebarTriggerSpace({ className, ...props }: ComponentProps<'span'>): ReactNode;
76
84
  /** A thin strip on the sidebar's edge. Clicking it toggles the sidebar. */
77
85
  export declare function NmSidebarRail({ className, label, ...props }: ComponentProps<'button'> & {
78
86
  label?: string;
@@ -142,9 +150,10 @@ export declare function NmSidebarMenuSubButton({ render, size, isActive, disable
142
150
  /** Not available. A link gets aria-disabled; a rendered button gets disabled. */
143
151
  disabled?: boolean;
144
152
  }): ReactNode;
145
- /** A chevron at the end of a row. In a collapsible row it points right and turns down when the row is open. With `menu` it points down, for a row that opens a menu. */
146
- export declare function NmSidebarChevron({ className, menu, ...props }: Omit<ComponentProps<typeof NmIcon>, 'id'> & {
153
+ /** A chevron at the end of a row. In a collapsible row it points right and turns down when the row is open. With `menu` it points down, for a row that opens a menu below. With `flyout` it keeps pointing right, for a row whose menu opens beside the sidebar. */
154
+ export declare function NmSidebarChevron({ className, menu, flyout, ...props }: Omit<ComponentProps<typeof NmIcon>, 'id'> & {
147
155
  menu?: boolean;
156
+ flyout?: boolean;
148
157
  }): ReactNode;
149
158
  /**
150
159
  * A dropdown menu opened from the sidebar. It closes the moment the sidebar goes out of view
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nordmanai/nif",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Nordman Interface Framework: the one UI and UX system for every Nordman product. Base UI, in the shadcn composition, styled by NIF tokens.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.13",
2
+ "version": "0.2.15",
3
3
  "versions": {
4
4
  "@animate-ui/react": "^2.4.2",
5
5
  "@base-ui/react": "^1.9.0",
@@ -171,6 +171,29 @@
171
171
  .nm-sidebar__trigger {
172
172
  flex-shrink: 0;
173
173
  }
174
+ /* Anchored: one toggle at the provider's top start corner, above the sidebar, that never moves. */
175
+ .nm-sidebar-provider > .nm-sidebar__trigger--anchored {
176
+ position: absolute;
177
+ top: var(--nm-space-12);
178
+ inset-inline-start: var(--nm-space-12);
179
+ z-index: calc(var(--nm-rail-z) + 1);
180
+ }
181
+ /* The header leaves the anchored toggle's room and lines its first row up with it. */
182
+ .nm-sidebar-provider:has(> .nm-sidebar__trigger--anchored) .nm-sidebar__header {
183
+ padding-block-start: var(--nm-space-12);
184
+ padding-inline-start: calc(var(--nm-space-12) + var(--nm-icon-control) + var(--nm-space-8));
185
+ min-height: calc(var(--nm-space-12) + var(--nm-icon-control) + var(--nm-space-8));
186
+ }
187
+ .nm-sidebar-provider:has(> .nm-sidebar__trigger--anchored) .nm-sidebar__header > .nm-sidebar__menu > .nm-sidebar__menu-item:first-child > .nm-sidebar__menu-button {
188
+ min-height: var(--nm-icon-control);
189
+ }
190
+ /* Holds the anchored toggle's place in a bar while the sidebar is closed. */
191
+ .nm-sidebar__trigger-space {
192
+ display: inline-block;
193
+ flex-shrink: 0;
194
+ width: var(--nm-icon-control);
195
+ height: var(--nm-icon-control);
196
+ }
174
197
  /* A header with the toggle in it is a row: the brand or switcher, then the toggle at the end. */
175
198
  .nm-sidebar__header:has(> .nm-sidebar__trigger) {
176
199
  flex-direction: row;
@@ -433,6 +456,9 @@
433
456
  padding-inline-end: var(--nm-icon-control);
434
457
  }
435
458
  .nm-sidebar__menu-item:has(> .nm-sidebar__menu-actions) > .nm-sidebar__menu-button {
459
+ padding-inline-end: var(--nm-icon-control);
460
+ }
461
+ .nm-sidebar__menu-item:has(> .nm-sidebar__menu-actions > :nth-child(2)) > .nm-sidebar__menu-button {
436
462
  padding-inline-end: calc(var(--nm-icon-control) * 2);
437
463
  }
438
464
  /* A group of actions at the end of a row. Its actions sit in flow inside it; the group takes the row's end. */
@@ -484,6 +510,11 @@
484
510
  .nm-sidebar__chevron[data-menu] {
485
511
  transform: none;
486
512
  }
513
+ [aria-expanded='true'] > .nm-sidebar__chevron[data-flyout],
514
+ [data-popup-open] > .nm-sidebar__chevron[data-flyout],
515
+ .nm-sidebar__chevron[data-flyout] {
516
+ transform: rotate(-90deg);
517
+ }
487
518
 
488
519
  /* The action at the end of a row sits over the row's end, centred on its height. */
489
520
  .nm-sidebar__menu-action {
@@ -295,10 +295,16 @@ export type NmSidebarTriggerProps = Omit<ComponentProps<'button'>, 'aria-label'>
295
295
  icon?: NmIconId;
296
296
  /** On by default: a tooltip with the name and the ⌘B shortcut. */
297
297
  tooltip?: boolean;
298
+ /**
299
+ * Keeps the toggle at one spot of the provider whether the sidebar is open or closed: it is taken out of
300
+ * the flow and anchored at the top start corner, above the sidebar. The header leaves room for it, and
301
+ * `NmSidebarTriggerSpace` holds its place in a bar while the sidebar is closed.
302
+ */
303
+ anchored?: boolean;
298
304
  };
299
305
 
300
306
  /** The button that opens and closes the sidebar. In the main area's bar, or in the sidebar's own header. Resting on it peeks the sidebar. */
301
- export function NmSidebarTrigger({ className, label, icon = 'panel-left', tooltip = true, onClick, onPointerEnter, onPointerLeave, ...props }: NmSidebarTriggerProps): ReactNode {
307
+ export function NmSidebarTrigger({ className, label, icon = 'panel-left', tooltip = true, anchored = false, onClick, onPointerEnter, onPointerLeave, ...props }: NmSidebarTriggerProps): ReactNode {
302
308
  const { open, isMobile, toggleSidebar, peekIn, peekOut } = useSidebar();
303
309
  const name = label ?? (open || isMobile ? 'Close sidebar' : 'Open sidebar');
304
310
  const button = (
@@ -307,7 +313,8 @@ export function NmSidebarTrigger({ className, label, icon = 'panel-left', toolti
307
313
  glyph={icon}
308
314
  label={name}
309
315
  tone="quiet"
310
- className={nmClass('nm-sidebar__trigger', className)}
316
+ data-anchored={anchored ? 'true' : undefined}
317
+ className={nmClass('nm-sidebar__trigger', anchored ? 'nm-sidebar__trigger--anchored' : null, className)}
311
318
  onClick={(event) => {
312
319
  onClick?.(event);
313
320
  toggleSidebar();
@@ -337,6 +344,11 @@ export function NmSidebarTrigger({ className, label, icon = 'panel-left', toolti
337
344
  );
338
345
  }
339
346
 
347
+ /** Holds the anchored trigger's place in a bar while the sidebar is closed, so the bar's content starts beside it. */
348
+ export function NmSidebarTriggerSpace({ className, ...props }: ComponentProps<'span'>): ReactNode {
349
+ return <span aria-hidden="true" data-slot="sidebar-trigger-space" className={nmClass('nm-sidebar__trigger-space', className)} {...props} />;
350
+ }
351
+
340
352
  /** A thin strip on the sidebar's edge. Clicking it toggles the sidebar. */
341
353
  export function NmSidebarRail({ className, label = 'Toggle sidebar', ...props }: ComponentProps<'button'> & { label?: string }): ReactNode {
342
354
  const { toggleSidebar } = useSidebar();
@@ -559,9 +571,9 @@ export function NmSidebarMenuSubButton({
559
571
  });
560
572
  }
561
573
 
562
- /** A chevron at the end of a row. In a collapsible row it points right and turns down when the row is open. With `menu` it points down, for a row that opens a menu. */
563
- export function NmSidebarChevron({ className, menu = false, ...props }: Omit<ComponentProps<typeof NmIcon>, 'id'> & { menu?: boolean }): ReactNode {
564
- return <NmIcon id="chevron" data-menu={menu ? '' : undefined} className={nmClass('nm-sidebar__chevron', className)} {...props} />;
574
+ /** A chevron at the end of a row. In a collapsible row it points right and turns down when the row is open. With `menu` it points down, for a row that opens a menu below. With `flyout` it keeps pointing right, for a row whose menu opens beside the sidebar. */
575
+ export function NmSidebarChevron({ className, menu = false, flyout = false, ...props }: Omit<ComponentProps<typeof NmIcon>, 'id'> & { menu?: boolean; flyout?: boolean }): ReactNode {
576
+ return <NmIcon id="chevron" data-menu={menu ? '' : undefined} data-flyout={flyout ? '' : undefined} className={nmClass('nm-sidebar__chevron', className)} {...props} />;
565
577
  }
566
578
 
567
579
  /**