@devalok/shilp-sutra 0.56.0 → 0.57.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/MIGRATION.md +107 -0
- package/dist/_chunks/badge-group.js +48 -34
- package/dist/_chunks/badge-group.js.map +1 -1
- package/dist/_chunks/chat.js +91 -75
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/document-preview.js +1 -1
- package/dist/_chunks/document-preview.js.map +1 -1
- package/dist/_chunks/emoji-suggestion.js +1 -1
- package/dist/_chunks/emoji-suggestion.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +2 -2
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/oauth-button.js +1 -1
- package/dist/_chunks/oauth-button.js.map +1 -1
- package/dist/_chunks/success.js +2 -2
- package/dist/_chunks/success.js.map +1 -1
- package/dist/_chunks/tree-view.js +1 -1
- package/dist/_chunks/tree-view.js.map +1 -1
- package/dist/_chunks/use-calendar.js +4 -4
- package/dist/_chunks/use-calendar.js.map +1 -1
- package/dist/ai/blocks/text.js +1 -1
- package/dist/ai/blocks/text.js.map +1 -1
- package/dist/ai/command-bar.js +9 -9
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +2 -2
- package/dist/ai/conversation.js.map +1 -1
- package/dist/composed/activity-feed.js +1 -1
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/avatar-group.d.ts +7 -2
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +3 -3
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/command-palette.js +7 -7
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/content-card.js +2 -2
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/date-picker/index.js +4 -4
- package/dist/composed/date-picker/index.js.map +1 -1
- package/dist/composed/diff.d.ts +23 -2
- package/dist/composed/diff.d.ts.map +1 -1
- package/dist/composed/diff.js +341 -260
- package/dist/composed/diff.js.map +1 -1
- package/dist/composed/emoji-picker.js +3 -3
- package/dist/composed/emoji-picker.js.map +1 -1
- package/dist/composed/error-boundary.js +2 -2
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/file-preview.js +1 -1
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/filter-bar.js +1 -1
- package/dist/composed/filter-bar.js.map +1 -1
- package/dist/composed/inline-edit.js +1 -1
- package/dist/composed/inline-edit.js.map +1 -1
- package/dist/composed/loading-skeleton.js +3 -3
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.js +1 -1
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/member-picker.js +1 -1
- package/dist/composed/member-picker.js.map +1 -1
- package/dist/composed/multi-select-popover.js +1 -1
- package/dist/composed/multi-select-popover.js.map +1 -1
- package/dist/composed/page-skeletons.js +4 -4
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/responsive-modal.js +1 -1
- package/dist/composed/responsive-modal.js.map +1 -1
- package/dist/composed/rich-chat-input.js +17 -17
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +5 -5
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/composed/schedule-view.js +5 -5
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -3
- package/dist/hooks/use-container-size.d.ts +18 -0
- package/dist/hooks/use-container-size.d.ts.map +1 -0
- package/dist/hooks/use-container-size.js +28 -0
- package/dist/hooks/use-container-size.js.map +1 -0
- package/dist/shell/app-shell.d.ts +101 -0
- package/dist/shell/app-shell.d.ts.map +1 -0
- package/dist/shell/app-shell.js +76 -0
- package/dist/shell/app-shell.js.map +1 -0
- package/dist/shell/bottom-navbar.js +2 -2
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +1 -0
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +6 -5
- package/dist/shell/notification-center.js +4 -4
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/notification-preferences.js +1 -1
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/shell/top-bar.js +7 -7
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/generate-scale.d.ts +49 -0
- package/dist/tokens/generate-scale.d.ts.map +1 -1
- package/dist/tokens/primitives.css +29 -26
- package/dist/tokens/semantic.css +98 -39
- package/dist/tokens/variants.css +1 -1
- package/dist/ui/accordion.js +1 -1
- package/dist/ui/accordion.js.map +1 -1
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/alert.d.ts.map +1 -1
- package/dist/ui/alert.js +7 -7
- package/dist/ui/alert.js.map +1 -1
- package/dist/ui/avatar.js +3 -3
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge-indicator.js +1 -1
- package/dist/ui/badge-indicator.js.map +1 -1
- package/dist/ui/badge.d.ts +47 -33
- package/dist/ui/badge.d.ts.map +1 -1
- package/dist/ui/banner.js +1 -1
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/button.js +4 -4
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.js +4 -4
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/chat/date-separator.d.ts +8 -1
- package/dist/ui/chat/date-separator.d.ts.map +1 -1
- package/dist/ui/checkbox.js +1 -1
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/code.js +2 -2
- package/dist/ui/code.js.map +1 -1
- package/dist/ui/color-input.js +3 -3
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.js +4 -4
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.js +4 -4
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-body.d.ts +3 -1
- package/dist/ui/data-table-body.d.ts.map +1 -1
- package/dist/ui/data-table-body.js +93 -92
- package/dist/ui/data-table-body.js.map +1 -1
- package/dist/ui/data-table-bulk-actions.d.ts +7 -1
- package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
- package/dist/ui/data-table-bulk-actions.js +14 -8
- package/dist/ui/data-table-bulk-actions.js.map +1 -1
- package/dist/ui/data-table-card.d.ts.map +1 -1
- package/dist/ui/data-table-card.js +66 -47
- package/dist/ui/data-table-card.js.map +1 -1
- package/dist/ui/data-table-context.d.ts +4 -0
- package/dist/ui/data-table-context.d.ts.map +1 -1
- package/dist/ui/data-table-context.js.map +1 -1
- package/dist/ui/data-table-header.d.ts.map +1 -1
- package/dist/ui/data-table-header.js +18 -15
- package/dist/ui/data-table-header.js.map +1 -1
- package/dist/ui/data-table-pagination.js +3 -3
- package/dist/ui/data-table-pagination.js.map +1 -1
- package/dist/ui/data-table-toolbar.d.ts +11 -1
- package/dist/ui/data-table-toolbar.d.ts.map +1 -1
- package/dist/ui/data-table-toolbar.js +7 -7
- package/dist/ui/data-table-toolbar.js.map +1 -1
- package/dist/ui/data-table.d.ts +52 -5
- package/dist/ui/data-table.d.ts.map +1 -1
- package/dist/ui/data-table.js +181 -169
- package/dist/ui/data-table.js.map +1 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dot.js +1 -1
- package/dist/ui/dot.js.map +1 -1
- package/dist/ui/dropdown-menu.js +4 -4
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/file-upload.js +3 -3
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/input-otp.js +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/input.js +8 -8
- package/dist/ui/input.js.map +1 -1
- package/dist/ui/menubar.js +5 -5
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.js +1 -1
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/number-input.js +3 -3
- package/dist/ui/number-input.js.map +1 -1
- package/dist/ui/pagination.js +1 -1
- package/dist/ui/pagination.js.map +1 -1
- package/dist/ui/progress-ring.js +2 -2
- package/dist/ui/progress-ring.js.map +1 -1
- package/dist/ui/progress.d.ts.map +1 -1
- package/dist/ui/progress.js +16 -12
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/radio.js +1 -1
- package/dist/ui/radio.js.map +1 -1
- package/dist/ui/select.js +5 -5
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sheet.js +1 -1
- package/dist/ui/sheet.js.map +1 -1
- package/dist/ui/sidebar.js +12 -12
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/slider.js +1 -1
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/split-button.js +3 -3
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.js +1 -1
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stepper.js +2 -2
- package/dist/ui/stepper.js.map +1 -1
- package/dist/ui/surface.d.ts.map +1 -1
- package/dist/ui/surface.js +2 -2
- package/dist/ui/surface.js.map +1 -1
- package/dist/ui/switch.js +1 -1
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +1 -1
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +1 -1
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/textarea.js +5 -5
- package/dist/ui/textarea.js.map +1 -1
- package/dist/ui/toast.js +1 -1
- package/dist/ui/toast.js.map +1 -1
- package/dist/ui/toggle.js +3 -3
- package/dist/ui/toggle.js.map +1 -1
- package/docs/components/composed/avatar-group.md +4 -4
- package/docs/components/composed/diff.md +10 -2
- package/docs/components/composed/schedule-view.md +1 -1
- package/docs/components/shell/app-shell.md +132 -0
- package/docs/components/ui/avatar.md +1 -1
- package/docs/components/ui/button.md +1 -1
- package/docs/components/ui/chat.md +7 -1
- package/docs/components/ui/color-swatch.md +1 -1
- package/docs/components/ui/data-table-toolbar.md +6 -2
- package/docs/components/ui/data-table.md +41 -7
- package/docs/components/ui/dot.md +1 -1
- package/docs/components/ui/progress.md +1 -0
- package/docs/components/ui/surface.md +3 -3
- package/docs/components/ui/table.md +2 -2
- package/docs/recipes/install-astro.md +1 -0
- package/docs/recipes/install-next-app-router.md +1 -0
- package/docs/recipes/install-remix.md +1 -0
- package/docs/recipes/install-tanstack-start.md +1 -0
- package/docs/recipes/install-vite.md +1 -0
- package/llms.txt +2 -1
- package/make-kit/Guidelines.md +2 -2
- package/make-kit/components/button.md +1 -1
- package/make-kit/components/card.md +6 -6
- package/make-kit/components/surface.md +4 -4
- package/make-kit/foundations/color.md +4 -4
- package/make-kit/foundations/dark-mode.md +1 -1
- package/make-kit/foundations/radius.md +1 -1
- package/make-kit/foundations/surfaces.md +9 -9
- package/mcp-manifest.json +350 -32
- package/package.json +6 -3
- package/skill/SKILL.md +12 -6
- package/skill/references/components.md +2 -1
- package/skill/references/setup-astro.md +1 -0
- package/skill/references/setup-next-app-router.md +1 -0
- package/skill/references/setup-remix.md +1 -0
- package/skill/references/setup-tanstack-start.md +1 -0
- package/skill/references/setup-vite.md +1 -0
- package/dist/primitives/react-toast.d.ts +0 -120
- package/dist/ui/charts/_internal/scales.d.ts +0 -5
- package/dist/ui/charts/_internal/scales.d.ts.map +0 -1
- package/dist/ui/lib/slot.d.ts +0 -8
- package/dist/ui/lib/slot.d.ts.map +0 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ComponentProps } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* AppShell — the frame that lets a bar span above BOTH the sidebar and the
|
|
4
|
+
* content.
|
|
5
|
+
*
|
|
6
|
+
* This is the one shape the library could not express. `SidebarProvider`
|
|
7
|
+
* renders a single flex row, so a bar can only live *inside* the content pane —
|
|
8
|
+
* which is why every shadcn example puts its `<header>` inside `SidebarInset`.
|
|
9
|
+
* Carbon can do it because its `Header` is a sibling above `SideNav`; this is
|
|
10
|
+
* that arrangement.
|
|
11
|
+
*
|
|
12
|
+
* Compose it with the components you already have:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <AppShell variant="inset" chrome="dim">
|
|
16
|
+
* <AppShellBar><TopBar … /></AppShellBar>
|
|
17
|
+
* <AppShellBody>
|
|
18
|
+
* <AppShellSidebar><Sidebar … /></AppShellSidebar>
|
|
19
|
+
* <AppShellCanvas>{children}</AppShellCanvas>
|
|
20
|
+
* </AppShellBody>
|
|
21
|
+
* </AppShell>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## The two inset arrangements
|
|
25
|
+
*
|
|
26
|
+
* `chrome` decides which plane the frame takes and which the work takes. It is
|
|
27
|
+
* not a lightness switch — the two planes swap roles, and which one is lighter
|
|
28
|
+
* flips between themes:
|
|
29
|
+
*
|
|
30
|
+
* | | light chrome | light canvas | dark chrome | dark canvas |
|
|
31
|
+
* |---|---|---|---|---|
|
|
32
|
+
* | `dim` | `#eeeeee` | `#ffffff` | `#0a0a0a` | `#171717` |
|
|
33
|
+
* | `bright` | `#ffffff` | `#eeeeee` | `#171717` | `#0a0a0a` |
|
|
34
|
+
*
|
|
35
|
+
* Both come from the same two tokens — `surface-sunken` and `surface-panel` —
|
|
36
|
+
* swapped. No third tier, and no per-theme special case.
|
|
37
|
+
*
|
|
38
|
+
* With `dim` the brand tint lands on the frame and the work area stays neutral.
|
|
39
|
+
* With `bright` it is the reverse. That choice matters more than the lightness:
|
|
40
|
+
* it decides whether your brand colour surrounds the work or sits underneath it.
|
|
41
|
+
*
|
|
42
|
+
* See docs/plans/2026-08-26-surface-model-rebuild.md.
|
|
43
|
+
*/
|
|
44
|
+
type Variant = 'flat' | 'inset';
|
|
45
|
+
type Chrome = 'dim' | 'bright';
|
|
46
|
+
export interface AppShellProps extends ComponentProps<'div'> {
|
|
47
|
+
/**
|
|
48
|
+
* `flat` — chrome and content share one surface; a hairline on the sidebar
|
|
49
|
+
* does the separating. `inset` — the content sits in a rounded panel inside
|
|
50
|
+
* the chrome. @default 'flat'
|
|
51
|
+
*/
|
|
52
|
+
variant?: Variant;
|
|
53
|
+
/**
|
|
54
|
+
* Which plane the chrome takes when `variant="inset"`. Ignored when flat.
|
|
55
|
+
* @default 'dim'
|
|
56
|
+
*/
|
|
57
|
+
chrome?: Chrome;
|
|
58
|
+
}
|
|
59
|
+
declare const AppShell: import('react').ForwardRefExoticComponent<Omit<AppShellProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
60
|
+
export interface AppShellBarProps extends ComponentProps<'header'> {
|
|
61
|
+
/**
|
|
62
|
+
* Draw a hairline under the bar. Defaults to `true` when flat (the bar is its
|
|
63
|
+
* own region and needs the edge) and `false` when inset (the bar is part of a
|
|
64
|
+
* continuous chrome plane, and a border would cut it in half).
|
|
65
|
+
*/
|
|
66
|
+
bordered?: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The full-width bar. Sits above the sidebar and the canvas, which is the whole
|
|
70
|
+
* point of this component.
|
|
71
|
+
*
|
|
72
|
+
* Height is a MINIMUM, not a fixed value — `TopBar` sizes to its content, and a
|
|
73
|
+
* two-line workspace switcher legitimately needs more room than a one-line one.
|
|
74
|
+
* Fixing it would clip.
|
|
75
|
+
*/
|
|
76
|
+
declare const AppShellBar: import('react').ForwardRefExoticComponent<Omit<AppShellBarProps, "ref"> & import('react').RefAttributes<HTMLElement>>;
|
|
77
|
+
/** The row beneath the bar: sidebar alongside canvas. */
|
|
78
|
+
declare const AppShellBody: import('react').ForwardRefExoticComponent<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
79
|
+
export interface AppShellSidebarProps extends ComponentProps<'div'> {
|
|
80
|
+
/** Render at the collapsed icon width instead of the full width. */
|
|
81
|
+
collapsed?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fixed-width slot for the sidebar. Widths come from the same constants
|
|
85
|
+
* `Sidebar` uses (16rem expanded, 3rem collapsed), so the two agree whether or
|
|
86
|
+
* not you compose them together.
|
|
87
|
+
*/
|
|
88
|
+
declare const AppShellSidebar: import('react').ForwardRefExoticComponent<Omit<AppShellSidebarProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
89
|
+
/**
|
|
90
|
+
* The content pane.
|
|
91
|
+
*
|
|
92
|
+
* When inset, the panel is flush to the bar and the sidebar and floats only off
|
|
93
|
+
* the bottom-right. It reads as the frame opening up rather than as a card
|
|
94
|
+
* dropped into the middle — a symmetric gap fights that.
|
|
95
|
+
*
|
|
96
|
+
* No shadow. The inset canvas is a frame, not a floating thing; shadows are
|
|
97
|
+
* reserved for what genuinely floats.
|
|
98
|
+
*/
|
|
99
|
+
declare const AppShellCanvas: import('react').ForwardRefExoticComponent<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
100
|
+
export { AppShell, AppShellBar, AppShellBody, AppShellCanvas, AppShellSidebar };
|
|
101
|
+
//# sourceMappingURL=app-shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/shell/app-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAK3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,KAAK,OAAO,GAAG,MAAM,GAAG,OAAO,CAAA;AAC/B,KAAK,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;AAe9B,MAAM,WAAW,aAAc,SAAQ,cAAc,CAAC,KAAK,CAAC;IAC1D;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,QAAA,MAAM,QAAQ,uHAmBb,CAAA;AAGD,MAAM,WAAW,gBAAiB,SAAQ,cAAc,CAAC,QAAQ,CAAC;IAChE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW,uHAiBhB,CAAA;AAGD,yDAAyD;AACzD,QAAA,MAAM,YAAY,2MAKjB,CAAA;AAGD,MAAM,WAAW,oBAAqB,SAAQ,cAAc,CAAC,KAAK,CAAC;IACjE,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,QAAA,MAAM,eAAe,8HAkBpB,CAAA;AAGD;;;;;;;;;GASG;AACH,QAAA,MAAM,cAAc,qMAgCnB,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,CAAA"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../ui/lib/utils.js";
|
|
3
|
+
import { createContext as t, forwardRef as n, useContext as r } from "react";
|
|
4
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/shell/app-shell.tsx
|
|
6
|
+
var a = t({
|
|
7
|
+
variant: "flat",
|
|
8
|
+
chrome: "dim"
|
|
9
|
+
});
|
|
10
|
+
function o(e) {
|
|
11
|
+
let t = r(a);
|
|
12
|
+
if (!t) throw Error(`${e} must be rendered inside <AppShell>.`);
|
|
13
|
+
return t;
|
|
14
|
+
}
|
|
15
|
+
var s = n(({ variant: t = "flat", chrome: n = "dim", className: r, children: o, ...s }, c) => /* @__PURE__ */ i(a.Provider, {
|
|
16
|
+
value: {
|
|
17
|
+
variant: t,
|
|
18
|
+
chrome: n
|
|
19
|
+
},
|
|
20
|
+
children: /* @__PURE__ */ i("div", {
|
|
21
|
+
ref: c,
|
|
22
|
+
className: e("flex min-h-svh flex-col", t === "flat" && "bg-surface-base", t === "inset" && n === "dim" && "bg-surface-sunken", t === "inset" && n === "bright" && "bg-surface-panel", r),
|
|
23
|
+
...s,
|
|
24
|
+
children: o
|
|
25
|
+
})
|
|
26
|
+
}));
|
|
27
|
+
s.displayName = "AppShell";
|
|
28
|
+
var c = n(({ bordered: t, className: n, ...r }, a) => {
|
|
29
|
+
let { variant: s } = o("AppShellBar");
|
|
30
|
+
return /* @__PURE__ */ i("header", {
|
|
31
|
+
ref: a,
|
|
32
|
+
className: e("flex w-full shrink-0 items-center", s === "flat" ? "min-h-ds-xl" : "min-h-ds-lg", (t ?? s === "flat") && "border-b border-surface-border", n),
|
|
33
|
+
...r
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
c.displayName = "AppShellBar";
|
|
37
|
+
var l = n(({ className: t, ...n }, r) => /* @__PURE__ */ i("div", {
|
|
38
|
+
ref: r,
|
|
39
|
+
className: e("flex min-h-0 flex-1", t),
|
|
40
|
+
...n
|
|
41
|
+
}));
|
|
42
|
+
l.displayName = "AppShellBody";
|
|
43
|
+
var u = n(({ collapsed: t = !1, className: n, style: r, ...a }, s) => {
|
|
44
|
+
let { variant: c } = o("AppShellSidebar");
|
|
45
|
+
return /* @__PURE__ */ i("div", {
|
|
46
|
+
ref: s,
|
|
47
|
+
"data-collapsed": t || void 0,
|
|
48
|
+
style: {
|
|
49
|
+
width: t ? "3rem" : "16rem",
|
|
50
|
+
...r
|
|
51
|
+
},
|
|
52
|
+
className: e("flex shrink-0 flex-col", c === "flat" && "border-r border-surface-border", n),
|
|
53
|
+
...a
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
u.displayName = "AppShellSidebar";
|
|
57
|
+
var d = n(({ className: t, children: n, ...r }, a) => {
|
|
58
|
+
let { variant: s, chrome: c } = o("AppShellCanvas");
|
|
59
|
+
return s === "flat" ? /* @__PURE__ */ i("main", {
|
|
60
|
+
ref: a,
|
|
61
|
+
className: e("flex min-w-0 flex-1 flex-col bg-surface-base", t),
|
|
62
|
+
...r,
|
|
63
|
+
children: n
|
|
64
|
+
}) : /* @__PURE__ */ i("div", {
|
|
65
|
+
className: "flex min-w-0 flex-1 pb-ds-03 pr-ds-03",
|
|
66
|
+
children: /* @__PURE__ */ i("main", {
|
|
67
|
+
ref: a,
|
|
68
|
+
className: e("flex min-w-0 flex-1 flex-col overflow-hidden rounded-overlay-lg", c === "dim" ? "bg-surface-panel" : "bg-surface-sunken", t),
|
|
69
|
+
...r,
|
|
70
|
+
children: n
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
d.displayName = "AppShellCanvas";
|
|
75
|
+
//#endregion
|
|
76
|
+
export { s as AppShell, c as AppShellBar, l as AppShellBody, d as AppShellCanvas, u as AppShellSidebar };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-shell.js","names":[],"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["'use client'\n\nimport type { ComponentProps } from 'react'\nimport { createContext, forwardRef, useContext } from 'react'\n\nimport { cn } from '../ui/lib/utils'\n\n/**\n * AppShell — the frame that lets a bar span above BOTH the sidebar and the\n * content.\n *\n * This is the one shape the library could not express. `SidebarProvider`\n * renders a single flex row, so a bar can only live *inside* the content pane —\n * which is why every shadcn example puts its `<header>` inside `SidebarInset`.\n * Carbon can do it because its `Header` is a sibling above `SideNav`; this is\n * that arrangement.\n *\n * Compose it with the components you already have:\n *\n * ```tsx\n * <AppShell variant=\"inset\" chrome=\"dim\">\n * <AppShellBar><TopBar … /></AppShellBar>\n * <AppShellBody>\n * <AppShellSidebar><Sidebar … /></AppShellSidebar>\n * <AppShellCanvas>{children}</AppShellCanvas>\n * </AppShellBody>\n * </AppShell>\n * ```\n *\n * ## The two inset arrangements\n *\n * `chrome` decides which plane the frame takes and which the work takes. It is\n * not a lightness switch — the two planes swap roles, and which one is lighter\n * flips between themes:\n *\n * | | light chrome | light canvas | dark chrome | dark canvas |\n * |---|---|---|---|---|\n * | `dim` | `#eeeeee` | `#ffffff` | `#0a0a0a` | `#171717` |\n * | `bright` | `#ffffff` | `#eeeeee` | `#171717` | `#0a0a0a` |\n *\n * Both come from the same two tokens — `surface-sunken` and `surface-panel` —\n * swapped. No third tier, and no per-theme special case.\n *\n * With `dim` the brand tint lands on the frame and the work area stays neutral.\n * With `bright` it is the reverse. That choice matters more than the lightness:\n * it decides whether your brand colour surrounds the work or sits underneath it.\n *\n * See docs/plans/2026-08-26-surface-model-rebuild.md.\n */\n\ntype Variant = 'flat' | 'inset'\ntype Chrome = 'dim' | 'bright'\n\ninterface ShellContext {\n variant: Variant\n chrome: Chrome\n}\n\nconst AppShellContext = createContext<ShellContext>({ variant: 'flat', chrome: 'dim' })\n\nfunction useAppShell(part: string): ShellContext {\n const ctx = useContext(AppShellContext)\n if (!ctx) throw new Error(`${part} must be rendered inside <AppShell>.`)\n return ctx\n}\n\nexport interface AppShellProps extends ComponentProps<'div'> {\n /**\n * `flat` — chrome and content share one surface; a hairline on the sidebar\n * does the separating. `inset` — the content sits in a rounded panel inside\n * the chrome. @default 'flat'\n */\n variant?: Variant\n /**\n * Which plane the chrome takes when `variant=\"inset\"`. Ignored when flat.\n * @default 'dim'\n */\n chrome?: Chrome\n}\n\nconst AppShell = forwardRef<HTMLDivElement, AppShellProps>(\n ({ variant = 'flat', chrome = 'dim', className, children, ...props }, ref) => (\n <AppShellContext.Provider value={{ variant, chrome }}>\n <div\n ref={ref}\n className={cn(\n 'flex min-h-svh flex-col',\n variant === 'flat' && 'bg-surface-base',\n // the two inset arrangements are the same pair of tokens, swapped\n variant === 'inset' && chrome === 'dim' && 'bg-surface-sunken',\n variant === 'inset' && chrome === 'bright' && 'bg-surface-panel',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </AppShellContext.Provider>\n ),\n)\nAppShell.displayName = 'AppShell'\n\nexport interface AppShellBarProps extends ComponentProps<'header'> {\n /**\n * Draw a hairline under the bar. Defaults to `true` when flat (the bar is its\n * own region and needs the edge) and `false` when inset (the bar is part of a\n * continuous chrome plane, and a border would cut it in half).\n */\n bordered?: boolean\n}\n\n/**\n * The full-width bar. Sits above the sidebar and the canvas, which is the whole\n * point of this component.\n *\n * Height is a MINIMUM, not a fixed value — `TopBar` sizes to its content, and a\n * two-line workspace switcher legitimately needs more room than a one-line one.\n * Fixing it would clip.\n */\nconst AppShellBar = forwardRef<HTMLElement, AppShellBarProps>(\n ({ bordered, className, ...props }, ref) => {\n const { variant } = useAppShell('AppShellBar')\n const showBorder = bordered ?? variant === 'flat'\n return (\n <header\n ref={ref}\n className={cn(\n 'flex w-full shrink-0 items-center',\n variant === 'flat' ? 'min-h-ds-xl' : 'min-h-ds-lg',\n showBorder && 'border-b border-surface-border',\n className,\n )}\n {...props}\n />\n )\n },\n)\nAppShellBar.displayName = 'AppShellBar'\n\n/** The row beneath the bar: sidebar alongside canvas. */\nconst AppShellBody = forwardRef<HTMLDivElement, ComponentProps<'div'>>(\n ({ className, ...props }, ref) => (\n // min-h-0 so a scrolling canvas does not stretch the row past the viewport\n <div ref={ref} className={cn('flex min-h-0 flex-1', className)} {...props} />\n ),\n)\nAppShellBody.displayName = 'AppShellBody'\n\nexport interface AppShellSidebarProps extends ComponentProps<'div'> {\n /** Render at the collapsed icon width instead of the full width. */\n collapsed?: boolean\n}\n\n/**\n * Fixed-width slot for the sidebar. Widths come from the same constants\n * `Sidebar` uses (16rem expanded, 3rem collapsed), so the two agree whether or\n * not you compose them together.\n */\nconst AppShellSidebar = forwardRef<HTMLDivElement, AppShellSidebarProps>(\n ({ collapsed = false, className, style, ...props }, ref) => {\n const { variant } = useAppShell('AppShellSidebar')\n return (\n <div\n ref={ref}\n data-collapsed={collapsed || undefined}\n style={{ width: collapsed ? '3rem' : '16rem', ...style }}\n className={cn(\n 'flex shrink-0 flex-col',\n // flat has no chrome plane of its own, so the edge does the work\n variant === 'flat' && 'border-r border-surface-border',\n className,\n )}\n {...props}\n />\n )\n },\n)\nAppShellSidebar.displayName = 'AppShellSidebar'\n\n/**\n * The content pane.\n *\n * When inset, the panel is flush to the bar and the sidebar and floats only off\n * the bottom-right. It reads as the frame opening up rather than as a card\n * dropped into the middle — a symmetric gap fights that.\n *\n * No shadow. The inset canvas is a frame, not a floating thing; shadows are\n * reserved for what genuinely floats.\n */\nconst AppShellCanvas = forwardRef<HTMLDivElement, ComponentProps<'main'>>(\n ({ className, children, ...props }, ref) => {\n const { variant, chrome } = useAppShell('AppShellCanvas')\n\n if (variant === 'flat') {\n return (\n <main\n ref={ref}\n className={cn('flex min-w-0 flex-1 flex-col bg-surface-base', className)}\n {...props}\n >\n {children}\n </main>\n )\n }\n\n return (\n <div className=\"flex min-w-0 flex-1 pb-ds-03 pr-ds-03\">\n <main\n ref={ref}\n className={cn(\n 'flex min-w-0 flex-1 flex-col overflow-hidden rounded-overlay-lg',\n chrome === 'dim' ? 'bg-surface-panel' : 'bg-surface-sunken',\n className,\n )}\n {...props}\n >\n {children}\n </main>\n </div>\n )\n },\n)\nAppShellCanvas.displayName = 'AppShellCanvas'\n\nexport { AppShell, AppShellBar, AppShellBody, AppShellCanvas, AppShellSidebar }\n"],"mappings":";;;;;AA0DA,IAAM,IAAkB,EAA4B;CAAE,SAAS;CAAQ,QAAQ;AAAM,CAAC;AAEtF,SAAS,EAAY,GAA4B;CAC/C,IAAM,IAAM,EAAW,CAAe;CACtC,IAAI,CAAC,GAAK,MAAU,MAAM,GAAG,EAAK,qCAAqC;CACvE,OAAO;AACT;AAgBA,IAAM,IAAW,GACd,EAAE,aAAU,QAAQ,YAAS,OAAO,cAAW,aAAU,GAAG,KAAS,MACpE,kBAAC,EAAgB,UAAjB;CAA0B,OAAO;EAAE;EAAS;CAAO;WACjD,kBAAC,OAAD;EACO;EACL,WAAW,EACT,2BACA,MAAY,UAAU,mBAEtB,MAAY,WAAW,MAAW,SAAS,qBAC3C,MAAY,WAAW,MAAW,YAAY,oBAC9C,CACF;EACA,GAAI;EAEH;CACE,CAAA;AACmB,CAAA,CAE9B;AACA,EAAS,cAAc;AAmBvB,IAAM,IAAc,GACjB,EAAE,aAAU,cAAW,GAAG,KAAS,MAAQ;CAC1C,IAAM,EAAE,eAAY,EAAY,aAAa;CAE7C,OACE,kBAAC,UAAD;EACO;EACL,WAAW,EACT,qCACA,MAAY,SAAS,gBAAgB,gBANxB,KAAY,MAAY,WAOvB,kCACd,CACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AACA,EAAY,cAAc;AAG1B,IAAM,IAAe,GAClB,EAAE,cAAW,GAAG,KAAS,MAExB,kBAAC,OAAD;CAAU;CAAK,WAAW,EAAG,uBAAuB,CAAS;CAAG,GAAI;AAAQ,CAAA,CAEhF;AACA,EAAa,cAAc;AAY3B,IAAM,IAAkB,GACrB,EAAE,eAAY,IAAO,cAAW,UAAO,GAAG,KAAS,MAAQ;CAC1D,IAAM,EAAE,eAAY,EAAY,iBAAiB;CACjD,OACE,kBAAC,OAAD;EACO;EACL,kBAAgB,KAAa,KAAA;EAC7B,OAAO;GAAE,OAAO,IAAY,SAAS;GAAS,GAAG;EAAM;EACvD,WAAW,EACT,0BAEA,MAAY,UAAU,kCACtB,CACF;EACA,GAAI;CACL,CAAA;AAEL,CACF;AACA,EAAgB,cAAc;AAY9B,IAAM,IAAiB,GACpB,EAAE,cAAW,aAAU,GAAG,KAAS,MAAQ;CAC1C,IAAM,EAAE,YAAS,cAAW,EAAY,gBAAgB;CAcxD,OAZI,MAAY,SAEZ,kBAAC,QAAD;EACO;EACL,WAAW,EAAG,gDAAgD,CAAS;EACvE,GAAI;EAEH;CACG,CAAA,IAKR,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,QAAD;GACO;GACL,WAAW,EACT,mEACA,MAAW,QAAQ,qBAAqB,qBACxC,CACF;GACA,GAAI;GAEH;EACG,CAAA;CACH,CAAA;AAET,CACF;AACA,EAAe,cAAc"}
|
|
@@ -107,7 +107,7 @@ var w = f.forwardRef(({ currentPath: a = "/", user: b = null, primaryItems: w =
|
|
|
107
107
|
...k,
|
|
108
108
|
ref: A,
|
|
109
109
|
"aria-label": "Mobile navigation",
|
|
110
|
-
className: n("fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-
|
|
110
|
+
className: n("fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-base px-ds-05 pb-safe pt-0 md:hidden", O),
|
|
111
111
|
children: [L.map((e) => /* @__PURE__ */ m(C, {
|
|
112
112
|
item: e,
|
|
113
113
|
isActive: I(e.href, e.exact),
|
|
@@ -164,7 +164,7 @@ var w = f.forwardRef(({ currentPath: a = "/", user: b = null, primaryItems: w =
|
|
|
164
164
|
href: t.href,
|
|
165
165
|
onClick: () => P(!1),
|
|
166
166
|
"aria-current": r ? "page" : void 0,
|
|
167
|
-
className: n("flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard", r ? "bg-surface-
|
|
167
|
+
className: n("flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard", r ? "bg-surface-panel-hover text-accent-11" : "text-surface-fg-subtle hover:bg-surface-panel-hover"),
|
|
168
168
|
children: [/* @__PURE__ */ m("span", {
|
|
169
169
|
className: "[&>svg]:h-ico-md [&>svg]:w-ico-md",
|
|
170
170
|
"aria-hidden": "true",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven (currentPath / user / items) and router-agnostic via `useLink`.\n * The \"More\" overflow uses the DS `Sheet` primitive, so it inherits focus trap,\n * scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring.\n */\nimport { IconDots } from '@tabler/icons-react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../ui/sheet'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** Icon shown when the item is active — e.g. a filled variant. Falls back to `icon`. */\n activeIcon?: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n /**\n * Roles allowed to see this item, matched against `user.role`. Omit to show\n * it to everyone. For arbitrary logic, use `canView` instead.\n */\n roles?: string[]\n /**\n * Visibility predicate — full control over whether this item renders.\n * Receives the current `user` (or `null`). Takes precedence over `roles`.\n */\n canView?: (user: BottomNavbarUser | null) => boolean\n}\n\n/**\n * Active-item indicator, animated between items on selection:\n * - `pill` (default) — Material-3 tonal pill behind the icon\n * - `underline` — top accent bar (Material-2 tab style)\n * - `tint` — subtle background tint on the whole active cell\n * - `none` — no shape; rely on the filled `activeIcon` + accent color (iOS)\n */\nexport type BottomNavIndicator = 'pill' | 'underline' | 'tint' | 'none'\n/** Show labels always (default) or only for the selected item (narrow viewports). */\nexport type BottomNavLabelVisibility = 'always' | 'selected'\n\nexport interface BottomNavbarProps extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** Current user. Drives per-item role gating via `item.roles` / `item.canView`. */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow sheet */\n moreItems?: BottomNavItem[]\n /** Active-indicator style. @default 'pill' */\n indicator?: BottomNavIndicator\n /** Label visibility. @default 'always' */\n labelVisibility?: BottomNavLabelVisibility\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n/** Per-item role/visibility gate. `canView` wins; else `roles` matched against user.role; else visible. */\nfunction itemVisible(item: BottomNavItem, user: BottomNavbarUser | null): boolean {\n if (item.canView) return item.canView(user)\n if (item.roles && item.roles.length > 0) {\n return !!user?.role && item.roles.includes(user.role)\n }\n return true\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (composes Badge)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n return (\n <Badge\n color=\"error\"\n variant=\"solid\"\n size=\"xs\"\n aria-label={`${count} notifications`}\n className=\"pointer-events-none absolute -end-1 -top-1 justify-center px-ds-01 tabular-nums motion-safe:animate-in motion-safe:zoom-in-75\"\n >\n {display}\n </Badge>\n )\n}\n\n// -----------------------------------------------------------------------\n// Active indicator (shared-element)\n// -----------------------------------------------------------------------\n\n/**\n * The active-item indicator. A single shared-element (`layoutId`) that SLIDES\n * to the selected item and fades in on first appearance. `none` renders\n * nothing (the filled `activeIcon` + accent color carry the state).\n */\nfunction ActiveIndicator({\n indicator,\n layoutId,\n reduced,\n}: {\n indicator: BottomNavIndicator\n layoutId: string\n reduced: boolean\n}) {\n if (indicator === 'none') return null\n const common = {\n layoutId,\n 'aria-hidden': true as const,\n transition: reduced ? { duration: 0 } : springs.snappy,\n initial: reduced ? false : { opacity: 0 },\n animate: { opacity: 1 },\n }\n if (indicator === 'underline') {\n return <motion.span {...common} className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9\" />\n }\n if (indicator === 'tint') {\n return <motion.span {...common} className=\"absolute inset-0 rounded-control bg-accent-3\" />\n }\n // pill (default) — Material-3 tonal pill behind the icon\n return <motion.span {...common} className=\"absolute inset-0 rounded-pill bg-accent-3\" />\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nconst itemClass =\n 'relative flex h-16 w-full min-w-0 cursor-pointer flex-col items-center justify-center gap-ds-02 px-ds-01 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard'\n\nfunction BottomNavLink({\n item,\n isActive,\n indicator,\n indicatorId,\n showLabel,\n reduced,\n}: {\n item: BottomNavItem\n isActive: boolean\n indicator: BottomNavIndicator\n indicatorId: string\n showLabel: boolean\n reduced: boolean\n}) {\n const Link = useLink()\n return (\n <motion.div\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className=\"flex min-w-0 flex-1 basis-0\"\n >\n <Link\n href={item.href}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(itemClass, isActive ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle')}\n >\n {/* tint covers the whole cell — sits behind the content */}\n {isActive && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {isActive && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {isActive && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <span className=\"relative [&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">\n {normalizeIcon(isActive && item.activeIcon ? item.activeIcon : item.icon)}\n </IconProvider>\n </span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n {showLabel && <span className=\"max-w-full truncate text-center\">{item.title}</span>}\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user = null,\n primaryItems = [],\n moreItems = [],\n indicator = 'pill',\n labelVisibility = 'always',\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const reduced = useReducedMotion() ?? false\n const [showMore, setShowMore] = useState(false)\n // Scope the shared-element layoutId per instance so two navbars don't animate into each other.\n const instanceId = React.useId()\n const indicatorId = `${instanceId}-nav-indicator`\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') return currentPath === path\n return currentPath.startsWith(path)\n }\n\n // Role/visibility gating\n const visiblePrimary = primaryItems.filter((item) => itemVisible(item, user))\n const visibleMore = moreItems.filter((item) => itemVisible(item, user))\n\n const isMoreActive = visibleMore.some((item) => isActive(item.href, item.exact))\n const moreSelected = showMore || isMoreActive\n const showMoreLabel = labelVisibility === 'always' || moreSelected\n\n return (\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-chrome px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {visiblePrimary.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n indicator={indicator}\n indicatorId={indicatorId}\n showLabel={labelVisibility === 'always' || isActive(item.href, item.exact)}\n reduced={reduced}\n />\n ))}\n\n {visibleMore.length > 0 && (\n <Sheet open={showMore} onOpenChange={setShowMore}>\n <SheetTrigger asChild>\n <motion.button\n type=\"button\"\n aria-label=\"More navigation options\"\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className={cn(\n itemClass,\n 'min-w-0 flex-1 basis-0',\n moreSelected ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle',\n )}\n >\n {moreSelected && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {moreSelected && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {moreSelected && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <Icon icon={IconDots} />\n </div>\n {showMoreLabel && <span className=\"max-w-full truncate text-center\">More</span>}\n </div>\n </motion.button>\n </SheetTrigger>\n\n <SheetContent side=\"bottom\" className=\"rounded-t-bubble\">\n <SheetHeader>\n <SheetTitle>More</SheetTitle>\n </SheetHeader>\n <div className=\"grid grid-cols-[repeat(auto-fit,minmax(72px,1fr))] gap-ds-03 pt-ds-04\">\n {visibleMore.map((item) => {\n const active = isActive(item.href, item.exact)\n return (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard',\n active\n ? 'bg-surface-raised-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-raised-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n <span className=\"max-w-full truncate text-center\">{item.title}</span>\n </Link>\n )\n })}\n </div>\n </SheetContent>\n </Sheet>\n )}\n </nav>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bottom-navbar.js","names":[],"sources":["../../src/shell/bottom-navbar.tsx"],"sourcesContent":["'use client'\n\n/**\n * BottomNavbar -- Mobile bottom navigation bar.\n *\n * Props-driven (currentPath / user / items) and router-agnostic via `useLink`.\n * The \"More\" overflow uses the DS `Sheet` primitive, so it inherits focus trap,\n * scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring.\n */\nimport { IconDots } from '@tabler/icons-react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useState } from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Icon } from '../ui/icon'\nimport { IconProvider } from '../ui/icon-context'\nimport type { IconInput } from '../ui/lib/icon-input'\nimport { springs } from '../ui/lib/motion'\nimport { normalizeIcon } from '../ui/lib/normalize-icon'\nimport { cn } from '../ui/lib/utils'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../ui/sheet'\nimport { useLink } from './link-context'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface BottomNavbarUser {\n name: string\n role?: string\n}\n\nexport interface BottomNavItem {\n title: string\n href: string\n /** Icon for this nav item. Accepts any `IconInput`. */\n icon: IconInput\n /** Icon shown when the item is active — e.g. a filled variant. Falls back to `icon`. */\n activeIcon?: IconInput\n /** When true, the route matches only when the path is exactly equal */\n exact?: boolean\n /** Notification badge count. 0 or undefined = hidden, 1–99 = shown, >99 = \"99+\" */\n badge?: number\n /**\n * Roles allowed to see this item, matched against `user.role`. Omit to show\n * it to everyone. For arbitrary logic, use `canView` instead.\n */\n roles?: string[]\n /**\n * Visibility predicate — full control over whether this item renders.\n * Receives the current `user` (or `null`). Takes precedence over `roles`.\n */\n canView?: (user: BottomNavbarUser | null) => boolean\n}\n\n/**\n * Active-item indicator, animated between items on selection:\n * - `pill` (default) — Material-3 tonal pill behind the icon\n * - `underline` — top accent bar (Material-2 tab style)\n * - `tint` — subtle background tint on the whole active cell\n * - `none` — no shape; rely on the filled `activeIcon` + accent color (iOS)\n */\nexport type BottomNavIndicator = 'pill' | 'underline' | 'tint' | 'none'\n/** Show labels always (default) or only for the selected item (narrow viewports). */\nexport type BottomNavLabelVisibility = 'always' | 'selected'\n\nexport interface BottomNavbarProps extends React.HTMLAttributes<HTMLElement> {\n /** Currently active pathname */\n currentPath?: string\n /** Current user. Drives per-item role gating via `item.roles` / `item.canView`. */\n user?: BottomNavbarUser | null\n /** Primary nav items shown directly in the bottom bar (max 4 recommended) */\n primaryItems?: BottomNavItem[]\n /** Additional items shown in the \"More\" overflow sheet */\n moreItems?: BottomNavItem[]\n /** Active-indicator style. @default 'pill' */\n indicator?: BottomNavIndicator\n /** Label visibility. @default 'always' */\n labelVisibility?: BottomNavLabelVisibility\n /** Additional className for the nav element */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n/** Per-item role/visibility gate. `canView` wins; else `roles` matched against user.role; else visible. */\nfunction itemVisible(item: BottomNavItem, user: BottomNavbarUser | null): boolean {\n if (item.canView) return item.canView(user)\n if (item.roles && item.roles.length > 0) {\n return !!user?.role && item.roles.includes(user.role)\n }\n return true\n}\n\n// -----------------------------------------------------------------------\n// NavBadge (composes Badge)\n// -----------------------------------------------------------------------\n\nfunction NavBadge({ count }: { count: number }) {\n if (!count || count <= 0) return null\n const display = count > 99 ? '99+' : String(count)\n return (\n <Badge\n color=\"error\"\n variant=\"solid\"\n size=\"xs\"\n aria-label={`${count} notifications`}\n className=\"pointer-events-none absolute -end-1 -top-1 justify-center px-ds-01 tabular-nums motion-safe:animate-in motion-safe:zoom-in-75\"\n >\n {display}\n </Badge>\n )\n}\n\n// -----------------------------------------------------------------------\n// Active indicator (shared-element)\n// -----------------------------------------------------------------------\n\n/**\n * The active-item indicator. A single shared-element (`layoutId`) that SLIDES\n * to the selected item and fades in on first appearance. `none` renders\n * nothing (the filled `activeIcon` + accent color carry the state).\n */\nfunction ActiveIndicator({\n indicator,\n layoutId,\n reduced,\n}: {\n indicator: BottomNavIndicator\n layoutId: string\n reduced: boolean\n}) {\n if (indicator === 'none') return null\n const common = {\n layoutId,\n 'aria-hidden': true as const,\n transition: reduced ? { duration: 0 } : springs.snappy,\n initial: reduced ? false : { opacity: 0 },\n animate: { opacity: 1 },\n }\n if (indicator === 'underline') {\n return <motion.span {...common} className=\"absolute top-0 h-[3px] w-full rounded-b-control-inner bg-accent-9\" />\n }\n if (indicator === 'tint') {\n return <motion.span {...common} className=\"absolute inset-0 rounded-control bg-accent-3\" />\n }\n // pill (default) — Material-3 tonal pill behind the icon\n return <motion.span {...common} className=\"absolute inset-0 rounded-pill bg-accent-3\" />\n}\n\n// -----------------------------------------------------------------------\n// BottomNavLink (internal)\n// -----------------------------------------------------------------------\n\nconst itemClass =\n 'relative flex h-16 w-full min-w-0 cursor-pointer flex-col items-center justify-center gap-ds-02 px-ds-01 pt-0 text-body-sm transition-colors duration-fast-02 ease-productive-standard'\n\nfunction BottomNavLink({\n item,\n isActive,\n indicator,\n indicatorId,\n showLabel,\n reduced,\n}: {\n item: BottomNavItem\n isActive: boolean\n indicator: BottomNavIndicator\n indicatorId: string\n showLabel: boolean\n reduced: boolean\n}) {\n const Link = useLink()\n return (\n <motion.div\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className=\"flex min-w-0 flex-1 basis-0\"\n >\n <Link\n href={item.href}\n aria-label={item.title}\n aria-current={isActive ? 'page' : undefined}\n className={cn(itemClass, isActive ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle')}\n >\n {/* tint covers the whole cell — sits behind the content */}\n {isActive && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {isActive && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {isActive && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <span className=\"relative [&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">\n {normalizeIcon(isActive && item.activeIcon ? item.activeIcon : item.icon)}\n </IconProvider>\n </span>\n {item.badge != null && <NavBadge count={item.badge} />}\n </div>\n {showLabel && <span className=\"max-w-full truncate text-center\">{item.title}</span>}\n </div>\n </Link>\n </motion.div>\n )\n}\n\n// -----------------------------------------------------------------------\n// BottomNavbar\n// -----------------------------------------------------------------------\n\nconst BottomNavbar = React.forwardRef<HTMLElement, BottomNavbarProps>(\n (\n {\n currentPath = '/',\n user = null,\n primaryItems = [],\n moreItems = [],\n indicator = 'pill',\n labelVisibility = 'always',\n className,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const reduced = useReducedMotion() ?? false\n const [showMore, setShowMore] = useState(false)\n // Scope the shared-element layoutId per instance so two navbars don't animate into each other.\n const instanceId = React.useId()\n const indicatorId = `${instanceId}-nav-indicator`\n\n const isActive = (path: string, exact = false) => {\n if (exact || path === '/') return currentPath === path\n return currentPath.startsWith(path)\n }\n\n // Role/visibility gating\n const visiblePrimary = primaryItems.filter((item) => itemVisible(item, user))\n const visibleMore = moreItems.filter((item) => itemVisible(item, user))\n\n const isMoreActive = visibleMore.some((item) => isActive(item.href, item.exact))\n const moreSelected = showMore || isMoreActive\n const showMoreLabel = labelVisibility === 'always' || moreSelected\n\n return (\n <nav\n {...props}\n ref={ref}\n aria-label=\"Mobile navigation\"\n className={cn(\n 'fixed bottom-0 start-0 end-0 z-sticky flex w-full flex-row items-stretch justify-between border-t border-surface-border-strong bg-surface-base px-ds-05 pb-safe pt-0 md:hidden',\n className,\n )}\n >\n {visiblePrimary.map((item) => (\n <BottomNavLink\n key={item.href}\n item={item}\n isActive={isActive(item.href, item.exact)}\n indicator={indicator}\n indicatorId={indicatorId}\n showLabel={labelVisibility === 'always' || isActive(item.href, item.exact)}\n reduced={reduced}\n />\n ))}\n\n {visibleMore.length > 0 && (\n <Sheet open={showMore} onOpenChange={setShowMore}>\n <SheetTrigger asChild>\n <motion.button\n type=\"button\"\n aria-label=\"More navigation options\"\n whileTap={reduced ? undefined : { scale: 0.96 }}\n transition={reduced ? { duration: 0 } : springs.snappy}\n className={cn(\n itemClass,\n 'min-w-0 flex-1 basis-0',\n moreSelected ? 'font-semibold text-accent-11' : 'text-surface-fg-subtle',\n )}\n >\n {moreSelected && indicator === 'tint' && (\n <ActiveIndicator indicator=\"tint\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative flex w-full min-w-0 flex-col items-center gap-ds-02\">\n {moreSelected && indicator === 'underline' && (\n <ActiveIndicator indicator=\"underline\" layoutId={indicatorId} reduced={reduced} />\n )}\n <div className=\"relative px-ds-04 py-ds-01\">\n {moreSelected && indicator === 'pill' && (\n <ActiveIndicator indicator=\"pill\" layoutId={indicatorId} reduced={reduced} />\n )}\n <Icon icon={IconDots} />\n </div>\n {showMoreLabel && <span className=\"max-w-full truncate text-center\">More</span>}\n </div>\n </motion.button>\n </SheetTrigger>\n\n <SheetContent side=\"bottom\" className=\"rounded-t-bubble\">\n <SheetHeader>\n <SheetTitle>More</SheetTitle>\n </SheetHeader>\n <div className=\"grid grid-cols-[repeat(auto-fit,minmax(72px,1fr))] gap-ds-03 pt-ds-04\">\n {visibleMore.map((item) => {\n const active = isActive(item.href, item.exact)\n return (\n <Link\n key={item.href}\n href={item.href}\n onClick={() => setShowMore(false)}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'flex min-w-0 flex-col items-center gap-ds-02b rounded-overlay-lg p-ds-04 text-body-sm transition-colors ease-productive-standard',\n active\n ? 'bg-surface-panel-hover text-accent-11'\n : 'text-surface-fg-subtle hover:bg-surface-panel-hover',\n )}\n >\n <span className=\"[&>svg]:h-ico-md [&>svg]:w-ico-md\" aria-hidden=\"true\">\n <IconProvider size=\"md\">{normalizeIcon(item.icon)}</IconProvider>\n </span>\n <span className=\"max-w-full truncate text-center\">{item.title}</span>\n </Link>\n )\n })}\n </div>\n </SheetContent>\n </Sheet>\n )}\n </nav>\n )\n },\n)\nBottomNavbar.displayName = 'BottomNavbar'\n\nexport { BottomNavbar }\n"],"mappings":"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"}
|
package/dist/shell/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
export { AppCommandPalette, type AppCommandPaletteProps, type AppCommandPaletteUser, type SearchResult, type SearchResultGroup, } from './app-command-palette.js';
|
|
15
15
|
export { BottomNavbar, type BottomNavbarProps, type BottomNavbarUser, type BottomNavItem, } from './bottom-navbar.js';
|
|
16
16
|
export { type CommandPageItem, type CommandRegistry, CommandRegistryProvider, type CommandRegistryProviderProps, useCommandRegistry, } from './command-registry.js';
|
|
17
|
+
export { AppShell, AppShellBar, type AppShellBarProps, AppShellBody, AppShellCanvas, type AppShellProps, AppShellSidebar, type AppShellSidebarProps, } from './app-shell.js';
|
|
17
18
|
export { LinkProvider, type LinkProviderProps, useLink } from './link-context.js';
|
|
18
19
|
export { type Notification, NotificationCenter, type NotificationCenterProps, } from './notification-center.js';
|
|
19
20
|
export { type NotificationPreference, NotificationPreferences, type NotificationPreferencesProps, type NotificationProject, } from './notification-preferences.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAC,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EACL,KAAK,YAAY,EACjB,kBAAkB,EAClB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EACL,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,QAAQ,EACR,WAAW,EACX,KAAK,gBAAgB,EACrB,YAAY,EACZ,cAAc,EACd,KAAK,aAAa,EAClB,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAC,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EACL,KAAK,YAAY,EACjB,kBAAkB,EAClB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACL,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,WAAW,CAAA"}
|
package/dist/shell/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { LinkProvider as e, useLink as t } from "../_chunks/link-context.js";
|
|
3
3
|
import { CommandRegistryProvider as n, useCommandRegistry as r } from "./command-registry.js";
|
|
4
4
|
import { AppCommandPalette as i } from "./app-command-palette.js";
|
|
5
|
+
import { AppShell as a, AppShellBar as o, AppShellBody as s, AppShellCanvas as c, AppShellSidebar as l } from "./app-shell.js";
|
|
5
6
|
import "./link-context.js";
|
|
6
|
-
import { BottomNavbar as
|
|
7
|
-
import { NotificationCenter as
|
|
8
|
-
import { NotificationPreferences as
|
|
9
|
-
import { TopBar as
|
|
10
|
-
export { i as AppCommandPalette, a as BottomNavbar, n as CommandRegistryProvider, e as LinkProvider,
|
|
7
|
+
import { BottomNavbar as u } from "./bottom-navbar.js";
|
|
8
|
+
import { NotificationCenter as d } from "./notification-center.js";
|
|
9
|
+
import { NotificationPreferences as f } from "./notification-preferences.js";
|
|
10
|
+
import { TopBar as p } from "./top-bar.js";
|
|
11
|
+
export { i as AppCommandPalette, a as AppShell, o as AppShellBar, s as AppShellBody, c as AppShellCanvas, l as AppShellSidebar, u as BottomNavbar, n as CommandRegistryProvider, e as LinkProvider, d as NotificationCenter, f as NotificationPreferences, p as TopBar, r as useCommandRegistry, t as useLink };
|
|
@@ -39,7 +39,7 @@ function te({ notification: e, onRead: n, onNavigate: i, getRoute: o, onDismiss:
|
|
|
39
39
|
onKeyDown: (e) => {
|
|
40
40
|
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), l());
|
|
41
41
|
},
|
|
42
|
-
className: t("group relative flex w-full cursor-pointer items-start gap-ds-04 px-ds-05 py-ds-04 text-left transition-colors duration-fast-02 ease-productive-standard", "hover:bg-surface-
|
|
42
|
+
className: t("group relative flex w-full cursor-pointer items-start gap-ds-04 px-ds-05 py-ds-04 text-left transition-colors duration-fast-02 ease-productive-standard", "hover:bg-surface-panel-hover", !e.isRead && "bg-accent-1"),
|
|
43
43
|
children: [
|
|
44
44
|
/* @__PURE__ */ x("div", {
|
|
45
45
|
className: "mt-ds-02b flex shrink-0",
|
|
@@ -92,7 +92,7 @@ function te({ notification: e, onRead: n, onNavigate: i, getRoute: o, onDismiss:
|
|
|
92
92
|
t.stopPropagation(), s(e.id);
|
|
93
93
|
},
|
|
94
94
|
onKeyDown: (e) => e.stopPropagation(),
|
|
95
|
-
className: "absolute right-ds-03 top-ds-03 hidden rounded-control-inner p-ds-01 text-surface-fg-subtle transition-[color,background-color,opacity] duration-fast-01 ease-productive-standard hover:bg-surface-
|
|
95
|
+
className: "absolute right-ds-03 top-ds-03 hidden rounded-control-inner p-ds-01 text-surface-fg-subtle transition-[color,background-color,opacity] duration-fast-01 ease-productive-standard hover:bg-surface-panel-hover hover:text-surface-fg-muted group-hover:flex group-focus-within:flex",
|
|
96
96
|
children: /* @__PURE__ */ x(r, {
|
|
97
97
|
icon: E,
|
|
98
98
|
size: "sm"
|
|
@@ -149,7 +149,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
|
|
|
149
149
|
className: "flex flex-col items-center justify-center px-ds-05 py-ds-09",
|
|
150
150
|
children: [
|
|
151
151
|
/* @__PURE__ */ x("div", {
|
|
152
|
-
className: "flex h-ds-lg w-ds-lg items-center justify-center rounded-pill bg-surface-
|
|
152
|
+
className: "flex h-ds-lg w-ds-lg items-center justify-center rounded-pill bg-surface-panel",
|
|
153
153
|
children: /* @__PURE__ */ x(r, {
|
|
154
154
|
icon: T,
|
|
155
155
|
size: "lg",
|
|
@@ -202,7 +202,7 @@ var M = _.forwardRef(({ notifications: a = [], unreadCount: f, open: g, onOpenCh
|
|
|
202
202
|
...H,
|
|
203
203
|
ref: U,
|
|
204
204
|
"aria-label": q > 0 ? `Notifications, ${q} unread` : "Notifications",
|
|
205
|
-
className: t("relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-
|
|
205
|
+
className: t("relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-panel text-surface-fg-muted transition-colors ease-productive-standard hover:bg-surface-panel-hover", V),
|
|
206
206
|
children: [/* @__PURE__ */ x(r, {
|
|
207
207
|
icon: C,
|
|
208
208
|
size: "sm"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-center.js","names":[],"sources":["../../src/shell/notification-center.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationCenter -- Popover-based notification panel.\n *\n * Props-driven: accepts notifications array and callbacks instead of\n * reading from Zustand stores or using Remix useNavigate.\n */\nimport { IconBell, IconChecks, IconInbox, IconX } from '@tabler/icons-react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useCallback,useRef } from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport { durations } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport {\n Sheet,\n SheetContent,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationAction {\n /** Button label */\n label: string\n /** Visual style — 'primary' is filled, 'default' is ghost, 'danger' is red ghost */\n variant?: 'primary' | 'default' | 'danger'\n /** Called when the action button is clicked. Receives the notification id. */\n onClick: (id: string) => void\n}\n\nexport interface Notification {\n id: string\n title: string\n body?: string | null\n tier: 'INFO' | 'IMPORTANT' | 'CRITICAL'\n isRead: boolean\n createdAt: string\n entityType?: string | null\n entityId?: string | null\n projectId?: string | null\n project?: { title: string } | null\n /** Inline action buttons (e.g. Approve / Deny). Max 2–3 recommended. */\n actions?: NotificationAction[]\n}\n\nexport interface NotificationCenterProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** List of notifications to display */\n notifications?: Notification[]\n /** Count of unread notifications (derived from notifications if not provided) */\n unreadCount?: number\n /** Whether the popover is open (controlled mode) */\n open?: boolean\n /** Called when the popover open state changes */\n onOpenChange?: (open: boolean) => void\n /** Whether more notifications are loading */\n isLoading?: boolean\n /** Whether there are more notifications to fetch */\n hasMore?: boolean\n /** Called when the user scrolls near the bottom to load more */\n onFetchMore?: () => void\n /** Called to mark a single notification as read */\n onMarkRead?: (id: string) => void\n /** Called to mark all notifications as read */\n onMarkAllRead?: () => void\n /** Called when the user clicks a notification (e.g., navigate to entity) */\n onNavigate?: (path: string) => void\n /** Resolve a notification to a navigation path. Return null if no navigation. */\n getNotificationRoute?: (notification: Notification) => string | null\n /** Additional actions rendered in the header (e.g., settings button) */\n headerActions?: React.ReactNode\n /** Custom empty state to display when there are no notifications */\n emptyState?: React.ReactNode\n /** Content rendered below the notification list as a footer */\n footerSlot?: React.ReactNode\n /** Called when the user dismisses a notification. Renders an X button per row when provided. */\n onDismiss?: (id: string) => void\n /** Additional className for the popover content container */\n popoverClassName?: string\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n )\n}\n\nfunction getDateGroup(dateStr: string): string {\n const date = new Date(dateStr)\n const today = new Date()\n const yesterday = new Date()\n yesterday.setDate(today.getDate() - 1)\n\n if (isSameDay(date, today)) return 'Today'\n if (isSameDay(date, yesterday)) return 'Yesterday'\n return 'Earlier'\n}\n\nconst TIER_COLORS: Record<string, string> = {\n INFO: 'bg-info-9',\n IMPORTANT: 'bg-warning-9',\n CRITICAL: 'bg-error-9',\n}\n\n// -----------------------------------------------------------------------\n// NotificationItem (internal)\n// -----------------------------------------------------------------------\n\nfunction NotificationItem({\n notification,\n onRead,\n onNavigate,\n getRoute,\n onDismiss,\n}: {\n notification: Notification\n onRead: (id: string) => void\n onNavigate: (path: string) => void\n getRoute: (notification: Notification) => string | null\n onDismiss?: (id: string) => void\n}) {\n const route = getRoute(notification)\n\n const handleClick = () => {\n if (!notification.isRead) {\n onRead(notification.id)\n }\n if (route) {\n onNavigate(route)\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleClick()\n }\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n className={cn(\n 'group relative flex w-full cursor-pointer items-start gap-ds-04 px-ds-05 py-ds-04 text-left transition-colors duration-fast-02 ease-productive-standard',\n 'hover:bg-surface-raised',\n !notification.isRead && 'bg-accent-1',\n )}\n >\n {/* Tier dot — doubles as read/unread marker */}\n <div className=\"mt-ds-02b flex shrink-0\">\n <span\n className={cn(\n 'h-[8px] w-[8px] rounded-pill transition-opacity duration-fast-02 ease-productive-standard',\n TIER_COLORS[notification.tier] || TIER_COLORS.INFO,\n notification.isRead ? 'opacity-20' : 'opacity-100',\n )}\n />\n </div>\n\n {/* Content */}\n <div className=\"min-w-0 flex-1\">\n <TruncatedText\n as=\"p\"\n className={cn(\n 'text-body-md text-surface-fg',\n !notification.isRead && 'font-semibold',\n )}\n >\n {notification.title}\n </TruncatedText>\n {notification.body && (\n <p className=\"mt-ds-01 line-clamp-2 text-body-sm text-surface-fg-subtle\">\n {notification.body}\n </p>\n )}\n <div className=\"mt-ds-02 flex items-center gap-ds-03\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {formatRelativeTime(notification.createdAt)}\n </span>\n {notification.project && (\n <>\n <span className=\"text-surface-fg-subtle\">\n ·\n </span>\n <TruncatedText className=\"text-body-sm text-surface-fg-subtle\">\n {notification.project.title}\n </TruncatedText>\n </>\n )}\n </div>\n\n {/* Inline actions */}\n {notification.actions && notification.actions.length > 0 && (\n <div className=\"mt-ds-03 flex items-center gap-ds-02\">\n {notification.actions.map((action) => (\n <Button\n key={action.label}\n size=\"sm\"\n variant={action.variant === 'primary' ? 'solid' : 'ghost'}\n color={action.variant === 'danger' ? 'error' : 'accent'}\n onClick={(e) => {\n e.stopPropagation()\n action.onClick(notification.id)\n }}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n\n {/* Dismiss button */}\n {onDismiss && (\n <button\n type=\"button\"\n aria-label={`Dismiss notification: ${notification.title}`}\n onClick={(e) => {\n e.stopPropagation()\n onDismiss(notification.id)\n }}\n onKeyDown={(e) => e.stopPropagation()}\n className=\"absolute right-ds-03 top-ds-03 hidden rounded-control-inner p-ds-01 text-surface-fg-subtle transition-[color,background-color,opacity] duration-fast-01 ease-productive-standard hover:bg-surface-raised-hover hover:text-surface-fg-muted group-hover:flex group-focus-within:flex\"\n >\n <Icon icon={IconX} size=\"sm\" />\n </button>\n )}\n </div>\n )\n}\n\n// -----------------------------------------------------------------------\n// NotificationCenter\n// -----------------------------------------------------------------------\n\nconst NotificationCenter = React.forwardRef<HTMLButtonElement, NotificationCenterProps>(\n (\n {\n notifications = [],\n unreadCount: unreadCountProp,\n open,\n onOpenChange,\n isLoading = false,\n hasMore = false,\n onFetchMore,\n onMarkRead,\n onMarkAllRead,\n onNavigate,\n getNotificationRoute,\n onDismiss,\n headerActions,\n emptyState,\n footerSlot,\n popoverClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const scrollRef = useRef<HTMLDivElement>(null)\n const prefersReducedMotion = useReducedMotion()\n const isMobile = useIsMobile()\n\n const unreadCount =\n unreadCountProp ?? notifications.filter((n) => !n.isRead).length\n\n const getRoute = getNotificationRoute ?? (() => null)\n\n const handleNavigate = useCallback(\n (path: string) => {\n onOpenChange?.(false)\n onNavigate?.(path)\n },\n [onNavigate, onOpenChange],\n )\n\n const handleMarkRead = useCallback(\n (id: string) => {\n onMarkRead?.(id)\n },\n [onMarkRead],\n )\n\n const handleScroll = useCallback(() => {\n const el = scrollRef.current\n if (!el || isLoading || !hasMore) return\n if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) {\n onFetchMore?.()\n }\n }, [isLoading, hasMore, onFetchMore])\n\n // Group notifications by date\n const grouped = notifications.reduce<Record<string, Notification[]>>(\n (acc, n) => {\n const group = getDateGroup(n.createdAt)\n if (!acc[group]) acc[group] = []\n acc[group].push(n)\n return acc\n },\n {},\n )\n\n const groupOrder = ['Today', 'Yesterday', 'Earlier']\n\n // -- Shared panel content (used in both Popover and Sheet) --\n const panelContent = (\n <>\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <div className=\"flex items-center gap-ds-03\">\n {/* On mobile the SheetTitle provides the accessible heading; on desktop use h3 */}\n {!isMobile && (\n <h3 className=\"text-body-md font-semibold text-surface-fg\">\n Notifications\n </h3>\n )}\n {unreadCount > 0 && (\n <span className=\"flex h-5 min-w-5 items-center justify-center rounded-pill bg-accent-2 px-ds-02b text-body-sm font-semibold text-accent-11\">\n {unreadCount}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-ds-03\">\n {unreadCount > 0 && onMarkAllRead && (\n <button\n type=\"button\"\n onClick={onMarkAllRead}\n className=\"flex items-center gap-ds-02 text-body-sm text-surface-fg-subtle transition-colors hover:text-accent-11\"\n >\n <Icon icon={IconChecks} size=\"sm\" />\n Mark all read\n </button>\n )}\n {headerActions}\n </div>\n </div>\n\n {/* Notification list */}\n <div\n ref={scrollRef}\n onScroll={handleScroll}\n className={cn('overflow-y-auto', isMobile ? 'max-h-[60vh]' : 'max-h-[420px]')}\n >\n {notifications.length === 0 ? (\n emptyState || (\n <div className=\"flex flex-col items-center justify-center px-ds-05 py-ds-09\">\n <div className=\"flex h-ds-lg w-ds-lg items-center justify-center rounded-pill bg-surface-raised\">\n <Icon icon={IconInbox} size=\"lg\" className=\"text-surface-fg-subtle\" />\n </div>\n <p className=\"mt-ds-04 text-body-md text-surface-fg-subtle\">\n No notifications yet\n </p>\n <p className=\"mt-ds-02 text-body-sm text-surface-fg-subtle\">\n You're all caught up!\n </p>\n </div>\n )\n ) : (\n groupOrder.map((group) => {\n const items = grouped[group]\n if (!items || items.length === 0) return null\n return (\n <div key={group}>\n <div className=\"sticky top-0 z-raised bg-surface-overlay px-ds-05 py-ds-02b\">\n <span className=\"text-body-sm font-medium text-surface-fg-subtle\">\n {group}\n </span>\n </div>\n {items.map((notification, index) => (\n <motion.div\n key={notification.id}\n initial={prefersReducedMotion ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={prefersReducedMotion ? { duration: 0 } : { delay: index * 0.03 }}\n >\n <NotificationItem\n notification={notification}\n onRead={handleMarkRead}\n onNavigate={handleNavigate}\n getRoute={getRoute}\n onDismiss={onDismiss}\n />\n </motion.div>\n ))}\n </div>\n )\n })\n )}\n\n {isLoading && (\n <div className=\"flex items-center justify-center py-ds-05\">\n <Spinner />\n </div>\n )}\n </div>\n {footerSlot && (\n <div className=\"border-t border-surface-border-strong px-ds-05 py-ds-03\">\n {footerSlot}\n </div>\n )}\n </>\n )\n\n // -- Trigger button (shared between both modes) --\n const triggerButton = (\n <button\n {...props}\n ref={ref}\n aria-label={unreadCount > 0 ? `Notifications, ${unreadCount} unread` : 'Notifications'}\n className={cn(\n 'relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-raised text-surface-fg-muted transition-colors ease-productive-standard hover:bg-surface-raised-hover',\n className,\n )}\n >\n <Icon icon={IconBell} size=\"sm\" />\n {unreadCount > 0 && (\n <motion.span\n initial={{ rotate: 0 }}\n animate={prefersReducedMotion ? undefined : { rotate: [0, -3, 3, -1, 1, 0] }}\n transition={prefersReducedMotion ? { duration: 0 } : { duration: durations.slow01, ease: 'easeInOut' }}\n className=\"absolute -right-ds-01 -top-ds-01 flex h-4 min-w-4 items-center justify-center rounded-pill bg-accent-9 px-ds-02 text-body-xs font-semibold text-accent-fg\"\n >\n {unreadCount > 99 ? '99+' : unreadCount}\n </motion.span>\n )}\n </button>\n )\n\n // -- Mobile: render in a bottom Sheet --\n if (isMobile) {\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <SheetTrigger asChild>\n {triggerButton}\n </SheetTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n Notifications\n </TooltipContent>\n </Tooltip>\n\n <SheetContent side=\"bottom\" className={cn('p-0', popoverClassName)} responsive={false}>\n <SheetTitle className=\"px-ds-05 pt-ds-04 text-body-md font-semibold text-surface-fg\">\n Notifications\n </SheetTitle>\n {panelContent}\n </SheetContent>\n </Sheet>\n )\n }\n\n // -- Desktop: render in a Popover --\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n {triggerButton}\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n Notifications\n </TooltipContent>\n </Tooltip>\n\n <PopoverContent\n className={cn(\n 'w-[380px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating',\n popoverClassName,\n )}\n sideOffset={8}\n align=\"end\"\n >\n {panelContent}\n </PopoverContent>\n </Popover>\n )\n },\n)\nNotificationCenter.displayName = 'NotificationCenter'\n\nexport { NotificationCenter }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notification-center.js","names":[],"sources":["../../src/shell/notification-center.tsx"],"sourcesContent":["'use client'\n\n/**\n * NotificationCenter -- Popover-based notification panel.\n *\n * Props-driven: accepts notifications array and callbacks instead of\n * reading from Zustand stores or using Remix useNavigate.\n */\nimport { IconBell, IconChecks, IconInbox, IconX } from '@tabler/icons-react'\nimport { motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\nimport { useCallback,useRef } from 'react'\n\nimport { useIsMobile } from '../hooks/use-mobile'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { formatRelativeTime } from '../ui/lib/date-utils'\nimport { durations } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport {\n Sheet,\n SheetContent,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from '../ui/tooltip'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// -----------------------------------------------------------------------\n// Types\n// -----------------------------------------------------------------------\n\nexport interface NotificationAction {\n /** Button label */\n label: string\n /** Visual style — 'primary' is filled, 'default' is ghost, 'danger' is red ghost */\n variant?: 'primary' | 'default' | 'danger'\n /** Called when the action button is clicked. Receives the notification id. */\n onClick: (id: string) => void\n}\n\nexport interface Notification {\n id: string\n title: string\n body?: string | null\n tier: 'INFO' | 'IMPORTANT' | 'CRITICAL'\n isRead: boolean\n createdAt: string\n entityType?: string | null\n entityId?: string | null\n projectId?: string | null\n project?: { title: string } | null\n /** Inline action buttons (e.g. Approve / Deny). Max 2–3 recommended. */\n actions?: NotificationAction[]\n}\n\nexport interface NotificationCenterProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** List of notifications to display */\n notifications?: Notification[]\n /** Count of unread notifications (derived from notifications if not provided) */\n unreadCount?: number\n /** Whether the popover is open (controlled mode) */\n open?: boolean\n /** Called when the popover open state changes */\n onOpenChange?: (open: boolean) => void\n /** Whether more notifications are loading */\n isLoading?: boolean\n /** Whether there are more notifications to fetch */\n hasMore?: boolean\n /** Called when the user scrolls near the bottom to load more */\n onFetchMore?: () => void\n /** Called to mark a single notification as read */\n onMarkRead?: (id: string) => void\n /** Called to mark all notifications as read */\n onMarkAllRead?: () => void\n /** Called when the user clicks a notification (e.g., navigate to entity) */\n onNavigate?: (path: string) => void\n /** Resolve a notification to a navigation path. Return null if no navigation. */\n getNotificationRoute?: (notification: Notification) => string | null\n /** Additional actions rendered in the header (e.g., settings button) */\n headerActions?: React.ReactNode\n /** Custom empty state to display when there are no notifications */\n emptyState?: React.ReactNode\n /** Content rendered below the notification list as a footer */\n footerSlot?: React.ReactNode\n /** Called when the user dismisses a notification. Renders an X button per row when provided. */\n onDismiss?: (id: string) => void\n /** Additional className for the popover content container */\n popoverClassName?: string\n /** Additional className */\n className?: string\n}\n\n// -----------------------------------------------------------------------\n// Helpers\n// -----------------------------------------------------------------------\n\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n )\n}\n\nfunction getDateGroup(dateStr: string): string {\n const date = new Date(dateStr)\n const today = new Date()\n const yesterday = new Date()\n yesterday.setDate(today.getDate() - 1)\n\n if (isSameDay(date, today)) return 'Today'\n if (isSameDay(date, yesterday)) return 'Yesterday'\n return 'Earlier'\n}\n\nconst TIER_COLORS: Record<string, string> = {\n INFO: 'bg-info-9',\n IMPORTANT: 'bg-warning-9',\n CRITICAL: 'bg-error-9',\n}\n\n// -----------------------------------------------------------------------\n// NotificationItem (internal)\n// -----------------------------------------------------------------------\n\nfunction NotificationItem({\n notification,\n onRead,\n onNavigate,\n getRoute,\n onDismiss,\n}: {\n notification: Notification\n onRead: (id: string) => void\n onNavigate: (path: string) => void\n getRoute: (notification: Notification) => string | null\n onDismiss?: (id: string) => void\n}) {\n const route = getRoute(notification)\n\n const handleClick = () => {\n if (!notification.isRead) {\n onRead(notification.id)\n }\n if (route) {\n onNavigate(route)\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleClick()\n }\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n className={cn(\n 'group relative flex w-full cursor-pointer items-start gap-ds-04 px-ds-05 py-ds-04 text-left transition-colors duration-fast-02 ease-productive-standard',\n 'hover:bg-surface-panel-hover',\n !notification.isRead && 'bg-accent-1',\n )}\n >\n {/* Tier dot — doubles as read/unread marker */}\n <div className=\"mt-ds-02b flex shrink-0\">\n <span\n className={cn(\n 'h-[8px] w-[8px] rounded-pill transition-opacity duration-fast-02 ease-productive-standard',\n TIER_COLORS[notification.tier] || TIER_COLORS.INFO,\n notification.isRead ? 'opacity-20' : 'opacity-100',\n )}\n />\n </div>\n\n {/* Content */}\n <div className=\"min-w-0 flex-1\">\n <TruncatedText\n as=\"p\"\n className={cn(\n 'text-body-md text-surface-fg',\n !notification.isRead && 'font-semibold',\n )}\n >\n {notification.title}\n </TruncatedText>\n {notification.body && (\n <p className=\"mt-ds-01 line-clamp-2 text-body-sm text-surface-fg-subtle\">\n {notification.body}\n </p>\n )}\n <div className=\"mt-ds-02 flex items-center gap-ds-03\">\n <span className=\"text-body-sm text-surface-fg-subtle\">\n {formatRelativeTime(notification.createdAt)}\n </span>\n {notification.project && (\n <>\n <span className=\"text-surface-fg-subtle\">\n ·\n </span>\n <TruncatedText className=\"text-body-sm text-surface-fg-subtle\">\n {notification.project.title}\n </TruncatedText>\n </>\n )}\n </div>\n\n {/* Inline actions */}\n {notification.actions && notification.actions.length > 0 && (\n <div className=\"mt-ds-03 flex items-center gap-ds-02\">\n {notification.actions.map((action) => (\n <Button\n key={action.label}\n size=\"sm\"\n variant={action.variant === 'primary' ? 'solid' : 'ghost'}\n color={action.variant === 'danger' ? 'error' : 'accent'}\n onClick={(e) => {\n e.stopPropagation()\n action.onClick(notification.id)\n }}\n onKeyDown={(e) => e.stopPropagation()}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n\n {/* Dismiss button */}\n {onDismiss && (\n <button\n type=\"button\"\n aria-label={`Dismiss notification: ${notification.title}`}\n onClick={(e) => {\n e.stopPropagation()\n onDismiss(notification.id)\n }}\n onKeyDown={(e) => e.stopPropagation()}\n className=\"absolute right-ds-03 top-ds-03 hidden rounded-control-inner p-ds-01 text-surface-fg-subtle transition-[color,background-color,opacity] duration-fast-01 ease-productive-standard hover:bg-surface-panel-hover hover:text-surface-fg-muted group-hover:flex group-focus-within:flex\"\n >\n <Icon icon={IconX} size=\"sm\" />\n </button>\n )}\n </div>\n )\n}\n\n// -----------------------------------------------------------------------\n// NotificationCenter\n// -----------------------------------------------------------------------\n\nconst NotificationCenter = React.forwardRef<HTMLButtonElement, NotificationCenterProps>(\n (\n {\n notifications = [],\n unreadCount: unreadCountProp,\n open,\n onOpenChange,\n isLoading = false,\n hasMore = false,\n onFetchMore,\n onMarkRead,\n onMarkAllRead,\n onNavigate,\n getNotificationRoute,\n onDismiss,\n headerActions,\n emptyState,\n footerSlot,\n popoverClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const scrollRef = useRef<HTMLDivElement>(null)\n const prefersReducedMotion = useReducedMotion()\n const isMobile = useIsMobile()\n\n const unreadCount =\n unreadCountProp ?? notifications.filter((n) => !n.isRead).length\n\n const getRoute = getNotificationRoute ?? (() => null)\n\n const handleNavigate = useCallback(\n (path: string) => {\n onOpenChange?.(false)\n onNavigate?.(path)\n },\n [onNavigate, onOpenChange],\n )\n\n const handleMarkRead = useCallback(\n (id: string) => {\n onMarkRead?.(id)\n },\n [onMarkRead],\n )\n\n const handleScroll = useCallback(() => {\n const el = scrollRef.current\n if (!el || isLoading || !hasMore) return\n if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) {\n onFetchMore?.()\n }\n }, [isLoading, hasMore, onFetchMore])\n\n // Group notifications by date\n const grouped = notifications.reduce<Record<string, Notification[]>>(\n (acc, n) => {\n const group = getDateGroup(n.createdAt)\n if (!acc[group]) acc[group] = []\n acc[group].push(n)\n return acc\n },\n {},\n )\n\n const groupOrder = ['Today', 'Yesterday', 'Earlier']\n\n // -- Shared panel content (used in both Popover and Sheet) --\n const panelContent = (\n <>\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-surface-border-strong px-ds-05 py-ds-04\">\n <div className=\"flex items-center gap-ds-03\">\n {/* On mobile the SheetTitle provides the accessible heading; on desktop use h3 */}\n {!isMobile && (\n <h3 className=\"text-body-md font-semibold text-surface-fg\">\n Notifications\n </h3>\n )}\n {unreadCount > 0 && (\n <span className=\"flex h-5 min-w-5 items-center justify-center rounded-pill bg-accent-2 px-ds-02b text-body-sm font-semibold text-accent-11\">\n {unreadCount}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-ds-03\">\n {unreadCount > 0 && onMarkAllRead && (\n <button\n type=\"button\"\n onClick={onMarkAllRead}\n className=\"flex items-center gap-ds-02 text-body-sm text-surface-fg-subtle transition-colors hover:text-accent-11\"\n >\n <Icon icon={IconChecks} size=\"sm\" />\n Mark all read\n </button>\n )}\n {headerActions}\n </div>\n </div>\n\n {/* Notification list */}\n <div\n ref={scrollRef}\n onScroll={handleScroll}\n className={cn('overflow-y-auto', isMobile ? 'max-h-[60vh]' : 'max-h-[420px]')}\n >\n {notifications.length === 0 ? (\n emptyState || (\n <div className=\"flex flex-col items-center justify-center px-ds-05 py-ds-09\">\n <div className=\"flex h-ds-lg w-ds-lg items-center justify-center rounded-pill bg-surface-panel\">\n <Icon icon={IconInbox} size=\"lg\" className=\"text-surface-fg-subtle\" />\n </div>\n <p className=\"mt-ds-04 text-body-md text-surface-fg-subtle\">\n No notifications yet\n </p>\n <p className=\"mt-ds-02 text-body-sm text-surface-fg-subtle\">\n You're all caught up!\n </p>\n </div>\n )\n ) : (\n groupOrder.map((group) => {\n const items = grouped[group]\n if (!items || items.length === 0) return null\n return (\n <div key={group}>\n <div className=\"sticky top-0 z-raised bg-surface-overlay px-ds-05 py-ds-02b\">\n <span className=\"text-body-sm font-medium text-surface-fg-subtle\">\n {group}\n </span>\n </div>\n {items.map((notification, index) => (\n <motion.div\n key={notification.id}\n initial={prefersReducedMotion ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={prefersReducedMotion ? { duration: 0 } : { delay: index * 0.03 }}\n >\n <NotificationItem\n notification={notification}\n onRead={handleMarkRead}\n onNavigate={handleNavigate}\n getRoute={getRoute}\n onDismiss={onDismiss}\n />\n </motion.div>\n ))}\n </div>\n )\n })\n )}\n\n {isLoading && (\n <div className=\"flex items-center justify-center py-ds-05\">\n <Spinner />\n </div>\n )}\n </div>\n {footerSlot && (\n <div className=\"border-t border-surface-border-strong px-ds-05 py-ds-03\">\n {footerSlot}\n </div>\n )}\n </>\n )\n\n // -- Trigger button (shared between both modes) --\n const triggerButton = (\n <button\n {...props}\n ref={ref}\n aria-label={unreadCount > 0 ? `Notifications, ${unreadCount} unread` : 'Notifications'}\n className={cn(\n 'relative flex h-ds-sm-plus w-ds-sm-plus items-center justify-center rounded-pill border border-card-strong bg-surface-panel text-surface-fg-muted transition-colors ease-productive-standard hover:bg-surface-panel-hover',\n className,\n )}\n >\n <Icon icon={IconBell} size=\"sm\" />\n {unreadCount > 0 && (\n <motion.span\n initial={{ rotate: 0 }}\n animate={prefersReducedMotion ? undefined : { rotate: [0, -3, 3, -1, 1, 0] }}\n transition={prefersReducedMotion ? { duration: 0 } : { duration: durations.slow01, ease: 'easeInOut' }}\n className=\"absolute -right-ds-01 -top-ds-01 flex h-4 min-w-4 items-center justify-center rounded-pill bg-accent-9 px-ds-02 text-body-xs font-semibold text-accent-fg\"\n >\n {unreadCount > 99 ? '99+' : unreadCount}\n </motion.span>\n )}\n </button>\n )\n\n // -- Mobile: render in a bottom Sheet --\n if (isMobile) {\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <SheetTrigger asChild>\n {triggerButton}\n </SheetTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n Notifications\n </TooltipContent>\n </Tooltip>\n\n <SheetContent side=\"bottom\" className={cn('p-0', popoverClassName)} responsive={false}>\n <SheetTitle className=\"px-ds-05 pt-ds-04 text-body-md font-semibold text-surface-fg\">\n Notifications\n </SheetTitle>\n {panelContent}\n </SheetContent>\n </Sheet>\n )\n }\n\n // -- Desktop: render in a Popover --\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <Tooltip>\n <TooltipTrigger asChild>\n <PopoverTrigger asChild>\n {triggerButton}\n </PopoverTrigger>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\" align=\"center\">\n Notifications\n </TooltipContent>\n </Tooltip>\n\n <PopoverContent\n className={cn(\n 'w-[380px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating',\n popoverClassName,\n )}\n sideOffset={8}\n align=\"end\"\n >\n {panelContent}\n </PopoverContent>\n </Popover>\n )\n },\n)\nNotificationCenter.displayName = 'NotificationCenter'\n\nexport { NotificationCenter }\n"],"mappings":"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"}
|
|
@@ -88,7 +88,7 @@ var k = {
|
|
|
88
88
|
className: e("flex items-center gap-ds-05 py-ds-04", r < b.length - 1 && "border-b border-surface-border-strong"),
|
|
89
89
|
children: [
|
|
90
90
|
/* @__PURE__ */ C("div", {
|
|
91
|
-
className: "flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-
|
|
91
|
+
className: "flex h-ds-sm w-ds-sm shrink-0 items-center justify-center rounded-surface bg-surface-panel",
|
|
92
92
|
children: /* @__PURE__ */ C("span", {
|
|
93
93
|
className: "[&>svg]:h-ico-sm [&>svg]:w-ico-sm text-surface-fg-muted",
|
|
94
94
|
"aria-hidden": "true",
|