@exxatdesignux/ui 0.0.6 → 0.0.7
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/bin/init.mjs +29 -0
- package/package.json +7 -2
- package/template/.nvmrc +1 -0
- package/template/.prettierignore +7 -0
- package/template/.prettierrc +11 -0
- package/template/AGENTS.md +485 -0
- package/template/Logo/Exxat_Prism.svg +39 -0
- package/template/Logo/Exxat_one.svg +36 -0
- package/template/README.md +58 -0
- package/template/app/(app)/compliance/page.tsx +10 -0
- package/template/app/(app)/dashboard/loading.tsx +18 -0
- package/template/app/(app)/dashboard/page.tsx +36 -0
- package/template/app/(app)/data-list/[id]/page.tsx +28 -0
- package/template/app/(app)/data-list/new/page.tsx +31 -0
- package/template/app/(app)/data-list/page.tsx +10 -0
- package/template/app/(app)/error.tsx +43 -0
- package/template/app/(app)/help/page.tsx +34 -0
- package/template/app/(app)/layout.tsx +54 -0
- package/template/app/(app)/loading.tsx +18 -0
- package/template/app/(app)/question-bank/page.tsx +10 -0
- package/template/app/(app)/rotations/page.tsx +15 -0
- package/template/app/(app)/settings/page.tsx +17 -0
- package/template/app/(app)/sites/all/page.tsx +13 -0
- package/template/app/(app)/team/page.tsx +10 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/globals.css +1811 -0
- package/template/app/layout.tsx +95 -0
- package/template/app/page.tsx +9 -0
- package/template/components/.gitkeep +0 -0
- package/template/components/app-sidebar-dynamic.tsx +15 -0
- package/template/components/app-sidebar.tsx +901 -0
- package/template/components/ask-leo-composer.tsx +216 -0
- package/template/components/ask-leo-sidebar.tsx +509 -0
- package/template/components/chart-area-interactive.tsx +293 -0
- package/template/components/charts-overview.tsx +2321 -0
- package/template/components/command-menu-01.tsx +133 -0
- package/template/components/command-menu-02.tsx +386 -0
- package/template/components/command-menu.tsx +182 -0
- package/template/components/compliance-board-view.tsx +134 -0
- package/template/components/compliance-client.tsx +92 -0
- package/template/components/compliance-list-view.tsx +59 -0
- package/template/components/compliance-page-header.tsx +89 -0
- package/template/components/compliance-table.tsx +525 -0
- package/template/components/dashboard-onboarding-gallery.tsx +13 -0
- package/template/components/dashboard-onboarding.tsx +21 -0
- package/template/components/dashboard-promo-banner.tsx +67 -0
- package/template/components/dashboard-quota-progress-card.tsx +369 -0
- package/template/components/dashboard-report-charts.tsx +69 -0
- package/template/components/dashboard-section-heading.tsx +68 -0
- package/template/components/dashboard-tabs.tsx +598 -0
- package/template/components/data-list-client.tsx +239 -0
- package/template/components/data-list-table-cells.test.tsx +22 -0
- package/template/components/data-list-table-cells.tsx +173 -0
- package/template/components/data-list-table.tsx +879 -0
- package/template/components/data-table/filter-date-calendar.tsx +38 -0
- package/template/components/data-table/filter-text-value-input.tsx +77 -0
- package/template/components/data-table/index.tsx +1612 -0
- package/template/components/data-table/pagination.tsx +256 -0
- package/template/components/data-table/types.ts +91 -0
- package/template/components/data-table/use-table-state.ts +566 -0
- package/template/components/data-view-dashboard-charts-compliance.tsx +960 -0
- package/template/components/data-view-dashboard-charts-team.tsx +968 -0
- package/template/components/data-view-dashboard-charts.tsx +1668 -0
- package/template/components/data-views/board-card-primitives.tsx +93 -0
- package/template/components/data-views/index.ts +41 -0
- package/template/components/data-views/list-page-board-card.tsx +192 -0
- package/template/components/data-views/list-page-board-template.tsx +122 -0
- package/template/components/data-views/placement-board-card.tsx +262 -0
- package/template/components/export-drawer.tsx +375 -0
- package/template/components/exxat-product-logo.tsx +453 -0
- package/template/components/form-layout-01.tsx +131 -0
- package/template/components/getting-started.tsx +625 -0
- package/template/components/key-metrics.tsx +920 -0
- package/template/components/leo-insight-indicator.tsx +364 -0
- package/template/components/leo-typing-dots.tsx +121 -0
- package/template/components/list-hub-status-badge.tsx +51 -0
- package/template/components/list-page-dashboard-charts.tsx +18 -0
- package/template/components/nav-documents.tsx +89 -0
- package/template/components/nav-main.tsx +58 -0
- package/template/components/nav-secondary.tsx +64 -0
- package/template/components/nav-user.tsx +190 -0
- package/template/components/new-placement-back-btn.tsx +28 -0
- package/template/components/new-placement-form.tsx +1066 -0
- package/template/components/onboarding/index.ts +4 -0
- package/template/components/onboarding/onboarding-01.tsx +7 -0
- package/template/components/onboarding/onboarding-02.tsx +7 -0
- package/template/components/onboarding/onboarding-03.tsx +7 -0
- package/template/components/onboarding/onboarding-04.tsx +7 -0
- package/template/components/page-header.tsx +57 -0
- package/template/components/placement-detail.tsx +438 -0
- package/template/components/placements-board-view.tsx +404 -0
- package/template/components/placements-list-view.tsx +285 -0
- package/template/components/placements-page-header.tsx +160 -0
- package/template/components/placements-table-columns.tsx +639 -0
- package/template/components/product-switcher.tsx +116 -0
- package/template/components/question-bank-board-view.tsx +205 -0
- package/template/components/question-bank-client.tsx +77 -0
- package/template/components/question-bank-list-view.tsx +59 -0
- package/template/components/question-bank-page-header.tsx +89 -0
- package/template/components/question-bank-table.tsx +586 -0
- package/template/components/rotations-empty-state.tsx +47 -0
- package/template/components/rotations-panel-activator.tsx +8 -0
- package/template/components/secondary-nav.tsx +394 -0
- package/template/components/secondary-panel.tsx +239 -0
- package/template/components/section-cards.tsx +106 -0
- package/template/components/settings-appearance-card.tsx +424 -0
- package/template/components/settings-client.tsx +537 -0
- package/template/components/settings-form-row.tsx +42 -0
- package/template/components/sidebar-auto-collapse.tsx +23 -0
- package/template/components/sidebar-auto-open.tsx +18 -0
- package/template/components/sidebar-shell.tsx +37 -0
- package/template/components/site-header.tsx +93 -0
- package/template/components/sites-all-client.tsx +154 -0
- package/template/components/sites-board-view.tsx +67 -0
- package/template/components/sites-list-view.tsx +47 -0
- package/template/components/sites-table.tsx +312 -0
- package/template/components/system-banner-slot.tsx +66 -0
- package/template/components/table-properties/column-row.tsx +90 -0
- package/template/components/table-properties/draggable-list.ts +49 -0
- package/template/components/table-properties/drawer-button.tsx +231 -0
- package/template/components/table-properties/drawer.tsx +1102 -0
- package/template/components/table-properties/filter-card.tsx +251 -0
- package/template/components/table-properties/index.ts +22 -0
- package/template/components/table-properties/sort-card.tsx +59 -0
- package/template/components/table-properties/types.ts +124 -0
- package/template/components/task-list-panel.tsx +98 -0
- package/template/components/task-priority-badge.tsx +28 -0
- package/template/components/team-board-view.tsx +114 -0
- package/template/components/team-client.tsx +93 -0
- package/template/components/team-list-view.tsx +62 -0
- package/template/components/team-page-header.tsx +92 -0
- package/template/components/team-table.tsx +525 -0
- package/template/components/templates/list-page.tsx +576 -0
- package/template/components/templates/primary-page-template.tsx +56 -0
- package/template/components/theme-color-sync.tsx +32 -0
- package/template/components/theme-provider.tsx +71 -0
- package/template/components/tinted-icon-disc.tsx +53 -0
- package/template/components/ui/ai-thinking-surface.tsx +121 -0
- package/template/components/ui/avatar.tsx +1 -0
- package/template/components/ui/badge.tsx +1 -0
- package/template/components/ui/banner.tsx +1 -0
- package/template/components/ui/breadcrumb.tsx +1 -0
- package/template/components/ui/button.tsx +1 -0
- package/template/components/ui/calendar.tsx +1 -0
- package/template/components/ui/card.tsx +1 -0
- package/template/components/ui/chart.tsx +1 -0
- package/template/components/ui/checkbox.tsx +1 -0
- package/template/components/ui/coach-mark.tsx +1 -0
- package/template/components/ui/collapsible.tsx +1 -0
- package/template/components/ui/command.tsx +1 -0
- package/template/components/ui/date-picker-field.tsx +1 -0
- package/template/components/ui/dialog.tsx +1 -0
- package/template/components/ui/dot-pattern.tsx +159 -0
- package/template/components/ui/drag-handle-grip.tsx +1 -0
- package/template/components/ui/drawer.tsx +1 -0
- package/template/components/ui/dropdown-menu.tsx +1 -0
- package/template/components/ui/field.tsx +1 -0
- package/template/components/ui/form.tsx +1 -0
- package/template/components/ui/input-group.tsx +1 -0
- package/template/components/ui/input-mask.tsx +1 -0
- package/template/components/ui/input.tsx +1 -0
- package/template/components/ui/kbd.tsx +1 -0
- package/template/components/ui/label.tsx +1 -0
- package/template/components/ui/leo-icon.tsx +726 -0
- package/template/components/ui/payment-card-fields.tsx +1 -0
- package/template/components/ui/popover.tsx +1 -0
- package/template/components/ui/radio-group.tsx +1 -0
- package/template/components/ui/select.tsx +1 -0
- package/template/components/ui/selection-tile-grid.tsx +1 -0
- package/template/components/ui/separator.tsx +1 -0
- package/template/components/ui/sheet.tsx +1 -0
- package/template/components/ui/sidebar.tsx +1 -0
- package/template/components/ui/skeleton.tsx +1 -0
- package/template/components/ui/sonner.tsx +1 -0
- package/template/components/ui/status-badge.tsx +1 -0
- package/template/components/ui/table.tsx +1 -0
- package/template/components/ui/tabs.tsx +1 -0
- package/template/components/ui/textarea.tsx +1 -0
- package/template/components/ui/tip.tsx +1 -0
- package/template/components/ui/toggle-group.tsx +1 -0
- package/template/components/ui/toggle-switch.tsx +1 -0
- package/template/components/ui/toggle.tsx +1 -0
- package/template/components/ui/tooltip.tsx +1 -0
- package/template/components/ui/view-segmented-control.tsx +1 -0
- package/template/components.json +27 -0
- package/template/contexts/chart-variant-context.tsx +35 -0
- package/template/contexts/command-menu-context.tsx +28 -0
- package/template/contexts/dashboard-view-context.tsx +35 -0
- package/template/contexts/product-context.tsx +38 -0
- package/template/contexts/system-banner-context.tsx +127 -0
- package/template/docs/command-menu-pattern.md +45 -0
- package/template/docs/data-views-pattern.md +160 -0
- package/template/ecosystem.config.cjs +20 -0
- package/template/eslint.config.mjs +18 -0
- package/template/fontawesome-subset.manifest.json +190 -0
- package/template/hooks/.gitkeep +0 -0
- package/template/hooks/use-app-theme.ts +1 -0
- package/template/hooks/use-coach-mark.ts +1 -0
- package/template/hooks/use-mobile.ts +1 -0
- package/template/hooks/use-mod-key-label.ts +1 -0
- package/template/lib/.gitkeep +0 -0
- package/template/lib/ask-leo-route-context.ts +133 -0
- package/template/lib/chart-keyboard-selection.test.ts +20 -0
- package/template/lib/chart-keyboard-selection.ts +17 -0
- package/template/lib/chart-line-dash.ts +16 -0
- package/template/lib/coach-mark-registry.ts +68 -0
- package/template/lib/command-menu-config.ts +127 -0
- package/template/lib/command-menu-search-data.ts +44 -0
- package/template/lib/conditional-rule-match.ts +32 -0
- package/template/lib/dashboard-customize-coach-mark.ts +18 -0
- package/template/lib/dashboard-layout-merge.ts +63 -0
- package/template/lib/data-list-display-options.ts +35 -0
- package/template/lib/data-list-persistence.ts +280 -0
- package/template/lib/data-list-view-surface.ts +58 -0
- package/template/lib/data-list-view.ts +29 -0
- package/template/lib/data-view-dashboard-storage.ts +101 -0
- package/template/lib/date-filter.ts +8 -0
- package/template/lib/dev-log.test.ts +28 -0
- package/template/lib/dev-log.ts +8 -0
- package/template/lib/editable-target.ts +10 -0
- package/template/lib/floating-sheet-panel.ts +72 -0
- package/template/lib/initials-from-name.ts +7 -0
- package/template/lib/list-page-table-properties.ts +52 -0
- package/template/lib/list-status-badges.ts +168 -0
- package/template/lib/logo-dev.ts +12 -0
- package/template/lib/mock/compliance-kpi.ts +61 -0
- package/template/lib/mock/compliance.ts +146 -0
- package/template/lib/mock/dashboard.ts +105 -0
- package/template/lib/mock/navigation.tsx +231 -0
- package/template/lib/mock/placements-kpi.ts +134 -0
- package/template/lib/mock/placements.ts +183 -0
- package/template/lib/mock/question-bank-kpi.ts +61 -0
- package/template/lib/mock/question-bank.ts +142 -0
- package/template/lib/mock/sites-directory.ts +16 -0
- package/template/lib/mock/sites-kpi.ts +25 -0
- package/template/lib/mock/team-kpi.ts +60 -0
- package/template/lib/mock/team.ts +118 -0
- package/template/lib/motion-ui.ts +17 -0
- package/template/lib/placement-board-card-layout.ts +79 -0
- package/template/lib/placement-lifecycle.ts +5 -0
- package/template/lib/row-height.ts +10 -0
- package/template/lib/stock-portrait.ts +11 -0
- package/template/lib/utils.test.ts +13 -0
- package/template/lib/utils.ts +1 -0
- package/template/next.config.mjs +15 -0
- package/template/package.json +83 -0
- package/template/postcss.config.mjs +8 -0
- package/template/public/.gitkeep +0 -0
- package/template/public/Illustration/Rotation.svg +74 -0
- package/template/public/avatars/user.svg +11 -0
- package/template/public/favicon/favicon.ico +0 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/logos/exxat-one.svg +36 -0
- package/template/public/logos/exxat-prism.svg +39 -0
- package/template/public/mock-schools/emory.svg +4 -0
- package/template/public/mock-schools/rush.svg +4 -0
- package/template/scripts/fontawesome-subset-audit.mjs +190 -0
- package/template/scripts/pm2-startup-macos.sh +13 -0
- package/template/skills-lock.json +10 -0
- package/template/stores/app-store.ts +33 -0
- package/template/tests/setup.ts +1 -0
- package/template/tsconfig.json +35 -0
- package/template/types/react-payment-inputs.d.ts +19 -0
- package/template/vitest.config.ts +18 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LeoInsightIndicator — reusable AI insight chip + popover.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* <LeoInsightIndicator insight={leoInsight} chartTitle="Placement Trends" />
|
|
8
|
+
* <LeoInsightIndicator insight={leoInsight} chartTitle="..." triggerLayout="plot-marker" />
|
|
9
|
+
*
|
|
10
|
+
* Two trigger layouts:
|
|
11
|
+
* "toolbar" — compact "Insight" pill in a card header corner (default)
|
|
12
|
+
* "plot-marker" — sits above an anchored data point on the chart canvas
|
|
13
|
+
*
|
|
14
|
+
* Palette is brand-only. Direction is communicated via icon SHAPE + kind LABEL
|
|
15
|
+
* + signed delta value (never colour alone — WCAG 1.4.1).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as React from "react"
|
|
19
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
20
|
+
import { Button } from "@/components/ui/button"
|
|
21
|
+
import { Kbd } from "@/components/ui/kbd"
|
|
22
|
+
import {
|
|
23
|
+
Tooltip,
|
|
24
|
+
TooltipContent,
|
|
25
|
+
TooltipTrigger,
|
|
26
|
+
} from "@/components/ui/tooltip"
|
|
27
|
+
import { AskLeoShortcutKbds, useAskLeo } from "@/components/ask-leo-sidebar"
|
|
28
|
+
import { cn } from "@/lib/utils"
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Types
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/** Optional anchor for drawing Leo on the plot (reference line + marker). */
|
|
35
|
+
export type ChartLeoInsightAnchor = {
|
|
36
|
+
/** Categorical value on the chart's X axis (e.g. month label). */
|
|
37
|
+
xValue: string
|
|
38
|
+
/** Fixed Y in data space; overrides yDataKeys / yCombine when set. */
|
|
39
|
+
yValue?: number
|
|
40
|
+
/** Row keys to combine; required when yValue is omitted. */
|
|
41
|
+
yDataKeys?: string[]
|
|
42
|
+
/** How to derive Y from yDataKeys: top of stacked bars = "sum", overlaid lines = "max". */
|
|
43
|
+
yCombine?: "max" | "sum"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Semantic kind of the insight — drives color, icon, and chip label.
|
|
48
|
+
* Defaults to "anomaly" when unset.
|
|
49
|
+
*/
|
|
50
|
+
export type ChartLeoInsightKind = "spike" | "dip" | "anomaly" | "trend"
|
|
51
|
+
|
|
52
|
+
/** Smart scan copy for a chart — opens in a popover; CTA can prefill Ask Leo. */
|
|
53
|
+
export type ChartLeoInsight = {
|
|
54
|
+
/** Short attention line (what stands out). */
|
|
55
|
+
headline: string
|
|
56
|
+
/** Plain-language explanation. */
|
|
57
|
+
explanation: string
|
|
58
|
+
/** Overrides the default prompt sent to Ask Leo. */
|
|
59
|
+
askLeoPrompt?: string
|
|
60
|
+
/**
|
|
61
|
+
* When set, pair with `ChartLeoPlotInsightOverlay` for an on-point pulse
|
|
62
|
+
* + guide line + chip positioned directly on the chart.
|
|
63
|
+
*/
|
|
64
|
+
anchor?: ChartLeoInsightAnchor
|
|
65
|
+
/** Semantic shape of the insight. Defaults to "anomaly". */
|
|
66
|
+
kind?: ChartLeoInsightKind
|
|
67
|
+
/** Magnitude chip, e.g. `{ value: "-24%", label: "vs last Dec" }`. */
|
|
68
|
+
delta?: { value: string; label?: string }
|
|
69
|
+
/** 2–4 supporting facts shown as bullets in the popover. */
|
|
70
|
+
bullets?: string[]
|
|
71
|
+
/** Optional secondary quick-actions alongside the Ask Leo CTA. */
|
|
72
|
+
actions?: Array<{
|
|
73
|
+
label: string
|
|
74
|
+
icon?: string
|
|
75
|
+
onSelect?: () => void
|
|
76
|
+
href?: string
|
|
77
|
+
}>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Internal constants
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
const LEO_KIND_META: Record<ChartLeoInsightKind, { icon: string; label: string }> = {
|
|
85
|
+
spike: { icon: "fa-arrow-trend-up", label: "Spike" },
|
|
86
|
+
dip: { icon: "fa-arrow-trend-down", label: "Dip" },
|
|
87
|
+
anomaly: { icon: "fa-wave-pulse", label: "Anomaly" },
|
|
88
|
+
trend: { icon: "fa-sparkles", label: "Insight" },
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const LEO_TOKENS = {
|
|
92
|
+
dotClass: "bg-brand",
|
|
93
|
+
iconClass: "text-brand",
|
|
94
|
+
softBgClass: "bg-brand/10",
|
|
95
|
+
borderClass: "border-brand/50",
|
|
96
|
+
cssVar: "var(--brand-color)",
|
|
97
|
+
} as const
|
|
98
|
+
|
|
99
|
+
function resolveLeoMeta(insight: ChartLeoInsight) {
|
|
100
|
+
return LEO_KIND_META[insight.kind ?? "anomaly"]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
// Component
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Reusable Leo insight chip + popover.
|
|
109
|
+
*
|
|
110
|
+
* Renders a pill trigger button that opens a Radix Popover containing:
|
|
111
|
+
* - Header: "Leo spotted" serif label + kind chip + close button
|
|
112
|
+
* - Body: headline, delta label, explanation, optional bullets
|
|
113
|
+
* - Footer: optional secondary actions + full-width "Ask Leo" CTA
|
|
114
|
+
*/
|
|
115
|
+
export function LeoInsightIndicator({
|
|
116
|
+
insight,
|
|
117
|
+
chartTitle,
|
|
118
|
+
triggerLayout = "toolbar",
|
|
119
|
+
}: {
|
|
120
|
+
insight: ChartLeoInsight
|
|
121
|
+
chartTitle: string
|
|
122
|
+
triggerLayout?: "toolbar" | "plot-marker"
|
|
123
|
+
}) {
|
|
124
|
+
const { openWithPrompt } = useAskLeo()
|
|
125
|
+
const [open, setOpen] = React.useState(false)
|
|
126
|
+
const titleId = React.useId()
|
|
127
|
+
const descriptionId = React.useId()
|
|
128
|
+
|
|
129
|
+
const defaultPrompt =
|
|
130
|
+
insight.askLeoPrompt ??
|
|
131
|
+
`For the chart "${chartTitle}": ${insight.headline} — ${insight.explanation} What should we do next?`
|
|
132
|
+
|
|
133
|
+
const meta = resolveLeoMeta(insight)
|
|
134
|
+
const isPlot = triggerLayout === "plot-marker"
|
|
135
|
+
const deltaValue = insight.delta?.value
|
|
136
|
+
|
|
137
|
+
const directionLabel =
|
|
138
|
+
insight.kind === "dip" ? "decreased" :
|
|
139
|
+
insight.kind === "spike" ? "increased" :
|
|
140
|
+
insight.kind === "anomaly" ? "anomaly detected" : "insight"
|
|
141
|
+
|
|
142
|
+
const ariaFull = deltaValue
|
|
143
|
+
? `Leo insight: ${directionLabel} ${deltaValue}. ${insight.headline}.`
|
|
144
|
+
: `Leo insight: ${insight.headline}.`
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
148
|
+
<PopoverTrigger asChild>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
aria-label={ariaFull}
|
|
152
|
+
aria-expanded={open}
|
|
153
|
+
aria-haspopup="dialog"
|
|
154
|
+
className={cn(
|
|
155
|
+
// WCAG 2.5.5 Target Size (AA): 28×28 min.
|
|
156
|
+
"relative inline-flex h-7 min-h-7 shrink-0 items-center gap-1.5 rounded-full border bg-card px-2.5 text-xs font-semibold shadow-sm",
|
|
157
|
+
"text-foreground",
|
|
158
|
+
LEO_TOKENS.borderClass,
|
|
159
|
+
"transition-[transform,background-color] duration-150",
|
|
160
|
+
"hover:-translate-y-[0.5px] hover:bg-card",
|
|
161
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
{/* Soft brand fill */}
|
|
165
|
+
<span
|
|
166
|
+
aria-hidden
|
|
167
|
+
className={cn("pointer-events-none absolute inset-0 rounded-full", LEO_TOKENS.softBgClass)}
|
|
168
|
+
/>
|
|
169
|
+
<i
|
|
170
|
+
className={cn("fa-solid", meta.icon, "relative text-[12px]", LEO_TOKENS.iconClass)}
|
|
171
|
+
aria-hidden="true"
|
|
172
|
+
/>
|
|
173
|
+
{deltaValue ? (
|
|
174
|
+
<span className="relative tabular-nums">{deltaValue}</span>
|
|
175
|
+
) : !isPlot ? (
|
|
176
|
+
<span className="relative hidden sm:inline">Insight</span>
|
|
177
|
+
) : null}
|
|
178
|
+
</button>
|
|
179
|
+
</PopoverTrigger>
|
|
180
|
+
|
|
181
|
+
<PopoverContent
|
|
182
|
+
className={cn(
|
|
183
|
+
"relative w-[min(20rem,calc(100vw-1.5rem))] overflow-hidden p-0",
|
|
184
|
+
"border border-border bg-background shadow-xl",
|
|
185
|
+
"hc:border-border hc:bg-card hc:shadow-none",
|
|
186
|
+
"forced-colors:border forced-colors:border-[CanvasText] forced-colors:bg-[Canvas] forced-colors:shadow-none",
|
|
187
|
+
)}
|
|
188
|
+
align={isPlot ? "center" : "end"}
|
|
189
|
+
side={isPlot ? "top" : "bottom"}
|
|
190
|
+
sideOffset={isPlot ? 12 : 6}
|
|
191
|
+
aria-labelledby={titleId}
|
|
192
|
+
aria-describedby={descriptionId}
|
|
193
|
+
>
|
|
194
|
+
{/* Ambient brand glow */}
|
|
195
|
+
<div
|
|
196
|
+
aria-hidden
|
|
197
|
+
className="pointer-events-none absolute inset-0 forced-colors:hidden"
|
|
198
|
+
style={{
|
|
199
|
+
background:
|
|
200
|
+
"radial-gradient(ellipse 120% 80% at 50% 100%, oklch(from var(--brand-color) l c h / 0.08) 0%, transparent 68%)",
|
|
201
|
+
}}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
{/* Screen-reader announcement */}
|
|
205
|
+
<span className="sr-only">
|
|
206
|
+
{`Leo spotted a ${meta.label.toLowerCase()}${deltaValue ? ` of ${deltaValue}` : ""}: ${insight.headline}`}
|
|
207
|
+
</span>
|
|
208
|
+
|
|
209
|
+
<div className="relative">
|
|
210
|
+
{/* ── Header ─────────────────────────────────────────────────── */}
|
|
211
|
+
<div className="flex items-center gap-2.5 border-b border-border px-3.5 pb-3 pt-3">
|
|
212
|
+
<span
|
|
213
|
+
aria-hidden
|
|
214
|
+
className="inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-brand/10"
|
|
215
|
+
>
|
|
216
|
+
<i className="fa-duotone fa-solid fa-star-christmas text-[11px] text-brand" aria-hidden="true" />
|
|
217
|
+
</span>
|
|
218
|
+
|
|
219
|
+
{/* Serif "Leo spotted" heading */}
|
|
220
|
+
<h1
|
|
221
|
+
className="text-base font-bold leading-tight tracking-tight text-foreground"
|
|
222
|
+
style={{ fontFamily: "var(--font-heading)" }}
|
|
223
|
+
>
|
|
224
|
+
Leo spotted
|
|
225
|
+
</h1>
|
|
226
|
+
|
|
227
|
+
{/* Kind + delta chip */}
|
|
228
|
+
<span
|
|
229
|
+
className={cn(
|
|
230
|
+
"ml-auto inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-semibold text-foreground",
|
|
231
|
+
LEO_TOKENS.softBgClass,
|
|
232
|
+
LEO_TOKENS.borderClass,
|
|
233
|
+
)}
|
|
234
|
+
>
|
|
235
|
+
<i
|
|
236
|
+
className={cn("fa-solid", meta.icon, "text-[10px]", LEO_TOKENS.iconClass)}
|
|
237
|
+
aria-hidden="true"
|
|
238
|
+
/>
|
|
239
|
+
<span>{meta.label}</span>
|
|
240
|
+
{deltaValue ? (
|
|
241
|
+
<>
|
|
242
|
+
<span aria-hidden className="text-muted-foreground">·</span>
|
|
243
|
+
<span className="tabular-nums">{deltaValue}</span>
|
|
244
|
+
</>
|
|
245
|
+
) : null}
|
|
246
|
+
</span>
|
|
247
|
+
|
|
248
|
+
{/* Close — WCAG 2.5.5: 28×28 target + Tooltip */}
|
|
249
|
+
<Tooltip>
|
|
250
|
+
<TooltipTrigger asChild>
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
aria-label="Close insight"
|
|
254
|
+
onClick={() => setOpen(false)}
|
|
255
|
+
className={cn(
|
|
256
|
+
"inline-flex size-7 min-h-7 min-w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground",
|
|
257
|
+
"transition-colors hover:bg-muted hover:text-foreground",
|
|
258
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
259
|
+
)}
|
|
260
|
+
>
|
|
261
|
+
<i className="fa-solid fa-xmark text-[12px]" aria-hidden="true" />
|
|
262
|
+
</button>
|
|
263
|
+
</TooltipTrigger>
|
|
264
|
+
<TooltipContent side="top" className="flex items-center gap-1.5">
|
|
265
|
+
<span>Close</span>
|
|
266
|
+
<Kbd>Esc</Kbd>
|
|
267
|
+
</TooltipContent>
|
|
268
|
+
</Tooltip>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* ── Body ───────────────────────────────────────────────────── */}
|
|
272
|
+
<div className="px-3.5 pb-3 pt-3">
|
|
273
|
+
<h3
|
|
274
|
+
id={titleId}
|
|
275
|
+
className="text-[13px] font-semibold leading-snug text-foreground"
|
|
276
|
+
>
|
|
277
|
+
{insight.headline}
|
|
278
|
+
</h3>
|
|
279
|
+
{insight.delta?.label ? (
|
|
280
|
+
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
|
281
|
+
{insight.delta.label}
|
|
282
|
+
</p>
|
|
283
|
+
) : null}
|
|
284
|
+
<p
|
|
285
|
+
id={descriptionId}
|
|
286
|
+
className="mt-2 text-[12.5px] leading-relaxed text-foreground"
|
|
287
|
+
>
|
|
288
|
+
{insight.explanation}
|
|
289
|
+
</p>
|
|
290
|
+
|
|
291
|
+
{insight.bullets && insight.bullets.length > 0 ? (
|
|
292
|
+
<ul className="mt-3 space-y-1.5">
|
|
293
|
+
{insight.bullets.map((b, i) => (
|
|
294
|
+
<li
|
|
295
|
+
key={i}
|
|
296
|
+
className="flex items-start gap-2 text-[12px] leading-snug text-foreground"
|
|
297
|
+
>
|
|
298
|
+
<span
|
|
299
|
+
aria-hidden
|
|
300
|
+
className={cn(
|
|
301
|
+
"mt-1.5 inline-block size-1.5 shrink-0 rounded-full",
|
|
302
|
+
LEO_TOKENS.dotClass,
|
|
303
|
+
)}
|
|
304
|
+
/>
|
|
305
|
+
<span className="min-w-0 flex-1">{b}</span>
|
|
306
|
+
</li>
|
|
307
|
+
))}
|
|
308
|
+
</ul>
|
|
309
|
+
) : null}
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{/* ── Footer ─────────────────────────────────────────────────── */}
|
|
313
|
+
<div className="flex flex-col gap-1.5 border-t border-border px-2.5 py-2">
|
|
314
|
+
{insight.actions?.map((a) => {
|
|
315
|
+
const content = (
|
|
316
|
+
<>
|
|
317
|
+
{a.icon ? (
|
|
318
|
+
<i className={cn("fa-light", a.icon, "text-[11px]")} aria-hidden="true" />
|
|
319
|
+
) : null}
|
|
320
|
+
<span>{a.label}</span>
|
|
321
|
+
</>
|
|
322
|
+
)
|
|
323
|
+
return (
|
|
324
|
+
<Button
|
|
325
|
+
key={a.label}
|
|
326
|
+
type="button"
|
|
327
|
+
variant="ghost"
|
|
328
|
+
size="sm"
|
|
329
|
+
className="h-8 min-h-8 w-full justify-center gap-1.5 px-2 text-[11.5px] text-foreground hover:text-foreground"
|
|
330
|
+
onClick={() => {
|
|
331
|
+
setOpen(false)
|
|
332
|
+
a.onSelect?.()
|
|
333
|
+
}}
|
|
334
|
+
asChild={!!a.href}
|
|
335
|
+
>
|
|
336
|
+
{a.href ? <a href={a.href}>{content}</a> : content}
|
|
337
|
+
</Button>
|
|
338
|
+
)
|
|
339
|
+
})}
|
|
340
|
+
<Button
|
|
341
|
+
type="button"
|
|
342
|
+
size="sm"
|
|
343
|
+
className="h-8 min-h-8 w-full justify-center gap-1.5 px-3 text-[11.5px]"
|
|
344
|
+
onClick={() => {
|
|
345
|
+
setOpen(false)
|
|
346
|
+
openWithPrompt(defaultPrompt)
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
<i
|
|
350
|
+
className="fa-duotone fa-solid fa-star-christmas text-[11px] text-primary-foreground"
|
|
351
|
+
aria-hidden="true"
|
|
352
|
+
/>
|
|
353
|
+
<span>Ask Leo</span>
|
|
354
|
+
<AskLeoShortcutKbds
|
|
355
|
+
variant="bare"
|
|
356
|
+
className="ml-0.5 hidden sm:inline-flex"
|
|
357
|
+
/>
|
|
358
|
+
</Button>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</PopoverContent>
|
|
362
|
+
</Popover>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Leo "thinking" dots.
|
|
5
|
+
*
|
|
6
|
+
* Character arc embedded in motion:
|
|
7
|
+
* 1. Enter — dots fade in from below, staggered.
|
|
8
|
+
* 2. Slow phase (~900 ms) — very gentle, meditative pulse. Almost still.
|
|
9
|
+
* This is the "hmm, let me think…" moment.
|
|
10
|
+
* 3. Fast phase — suddenly switches to an energetic, high-amplitude pulse.
|
|
11
|
+
* The "oh! I'm working on it" moment.
|
|
12
|
+
* 4. Exit — instead of vanishing, each dot continues its motion while
|
|
13
|
+
* floating upward and fading. Feels like thoughts dispersing into an
|
|
14
|
+
* answer. Runs inside an `<AnimatePresence>` at the call site.
|
|
15
|
+
*
|
|
16
|
+
* Used in the Ask Leo sidebar while a reply is pending; can be reused for
|
|
17
|
+
* any "thinking" affordance.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import * as React from "react"
|
|
21
|
+
import { motion, useReducedMotion, type Variants } from "motion/react"
|
|
22
|
+
import { cn } from "@/lib/utils"
|
|
23
|
+
|
|
24
|
+
type Phase = "slow" | "fast"
|
|
25
|
+
|
|
26
|
+
// Each dot reads its index from `custom` so its delay is a clean function of
|
|
27
|
+
// position, not hard-coded — keeps the stagger readable.
|
|
28
|
+
const dotVariants: Variants = {
|
|
29
|
+
// Slow: barely moving, meditative. Duration long, amplitudes tiny.
|
|
30
|
+
slow: (i: number) => ({
|
|
31
|
+
opacity: [0.55, 0.8, 0.55],
|
|
32
|
+
scale: [0.9, 1.0, 0.9],
|
|
33
|
+
y: 0,
|
|
34
|
+
transition: {
|
|
35
|
+
duration: 2.8,
|
|
36
|
+
repeat: Infinity,
|
|
37
|
+
ease: [0.45, 0.05, 0.5, 1],
|
|
38
|
+
delay: i * 0.32,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
// Fast: energetic, high-amplitude, "processing at full speed".
|
|
42
|
+
fast: (i: number) => ({
|
|
43
|
+
opacity: [0.6, 1, 0.6],
|
|
44
|
+
scale: [0.72, 1.28, 0.72],
|
|
45
|
+
y: 0,
|
|
46
|
+
transition: {
|
|
47
|
+
duration: 0.9,
|
|
48
|
+
repeat: Infinity,
|
|
49
|
+
ease: "easeInOut",
|
|
50
|
+
delay: i * 0.14,
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type LeoTypingDotsProps = {
|
|
56
|
+
className?: string
|
|
57
|
+
/** `status` = polite live region; `decorative` = aria-hidden only. */
|
|
58
|
+
variant?: "status" | "decorative"
|
|
59
|
+
/** Announced when variant is `status`. */
|
|
60
|
+
statusLabel?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function LeoTypingDots({
|
|
64
|
+
className,
|
|
65
|
+
variant = "status",
|
|
66
|
+
statusLabel = "Leo is thinking",
|
|
67
|
+
}: LeoTypingDotsProps) {
|
|
68
|
+
const reduced = useReducedMotion() ?? false
|
|
69
|
+
const [phase, setPhase] = React.useState<Phase>("slow")
|
|
70
|
+
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (reduced) return
|
|
73
|
+
// After the slow "settling" period, snap to the fast tempo.
|
|
74
|
+
const t = setTimeout(() => setPhase("fast"), 900)
|
|
75
|
+
return () => clearTimeout(t)
|
|
76
|
+
}, [reduced])
|
|
77
|
+
|
|
78
|
+
const ariaProps = variant === "decorative"
|
|
79
|
+
? ({ "aria-hidden": true } as const)
|
|
80
|
+
: ({ role: "status", "aria-live": "polite" } as const)
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<motion.span
|
|
84
|
+
{...ariaProps}
|
|
85
|
+
className={cn("inline-flex items-center gap-1", className)}
|
|
86
|
+
// Container fades together on the way out. Each dot's exit adds the motion.
|
|
87
|
+
initial={reduced ? false : { opacity: 0 }}
|
|
88
|
+
animate={{ opacity: 1 }}
|
|
89
|
+
exit={reduced ? { opacity: 0 } : {
|
|
90
|
+
opacity: 0,
|
|
91
|
+
transition: { duration: 0.6, ease: [0.4, 0, 0.2, 1] },
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
{variant === "status" && <span className="sr-only">{statusLabel}</span>}
|
|
95
|
+
{[0, 1, 2].map(i => (
|
|
96
|
+
<motion.span
|
|
97
|
+
key={i}
|
|
98
|
+
aria-hidden
|
|
99
|
+
className="inline-block size-1.5 rounded-full bg-brand"
|
|
100
|
+
custom={i}
|
|
101
|
+
variants={dotVariants}
|
|
102
|
+
initial={reduced ? false : { opacity: 0, scale: 0.5, y: 4 }}
|
|
103
|
+
animate={reduced ? { opacity: 0.9, scale: 1 } : phase}
|
|
104
|
+
// Exit: keep pulsing once more, float up, fade. Staggered so they
|
|
105
|
+
// leave in a little wave rather than simultaneously.
|
|
106
|
+
exit={reduced ? { opacity: 0 } : {
|
|
107
|
+
opacity: [0.9, 1, 0],
|
|
108
|
+
scale: [1, 1.35, 0.3],
|
|
109
|
+
y: [0, -4, -14],
|
|
110
|
+
transition: {
|
|
111
|
+
duration: 0.55,
|
|
112
|
+
delay: i * 0.06,
|
|
113
|
+
times: [0, 0.35, 1],
|
|
114
|
+
ease: [0.3, 0, 0.2, 1],
|
|
115
|
+
},
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
</motion.span>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Consistent status chip for list hubs (Team, Compliance, Question bank, future entities).
|
|
5
|
+
* Pair label + tint + icon from `lib/list-status-badges.ts`; do not hand-roll Badge markup per page.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react"
|
|
9
|
+
import { Badge } from "@/components/ui/badge"
|
|
10
|
+
import { cn } from "@/lib/utils"
|
|
11
|
+
|
|
12
|
+
/** Table column + list view rows — same shell as Team / Compliance / Question bank grids. */
|
|
13
|
+
export const LIST_HUB_STATUS_BADGE_TABLE_SHELL =
|
|
14
|
+
"inline-flex items-center gap-1 text-xs font-medium"
|
|
15
|
+
|
|
16
|
+
/** Kanban card badge row — fixed height, no default outline border clash. */
|
|
17
|
+
export const LIST_HUB_STATUS_BADGE_BOARD_SHELL =
|
|
18
|
+
"inline-flex h-6 items-center gap-1 border-0 px-2 py-1 text-xs font-medium leading-none shadow-none"
|
|
19
|
+
|
|
20
|
+
export interface ListHubStatusBadgeProps {
|
|
21
|
+
label: string
|
|
22
|
+
/** Tails from `*_STATUS_BADGE_CLASS` in `@/lib/list-status-badges` */
|
|
23
|
+
tintClassName: string
|
|
24
|
+
/** Font Awesome icon class suffix, e.g. `fa-circle-check` (paired with `fa-light` here). */
|
|
25
|
+
icon: string
|
|
26
|
+
/** `table` — DataTable cells and list rows; `board` — `ListPageBoardCardBadgeRow`. */
|
|
27
|
+
surface?: "table" | "board"
|
|
28
|
+
className?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ListHubStatusBadge({
|
|
32
|
+
label,
|
|
33
|
+
tintClassName,
|
|
34
|
+
icon,
|
|
35
|
+
surface = "table",
|
|
36
|
+
className,
|
|
37
|
+
}: ListHubStatusBadgeProps) {
|
|
38
|
+
return (
|
|
39
|
+
<Badge
|
|
40
|
+
variant="outline"
|
|
41
|
+
className={cn(
|
|
42
|
+
surface === "board" ? LIST_HUB_STATUS_BADGE_BOARD_SHELL : LIST_HUB_STATUS_BADGE_TABLE_SHELL,
|
|
43
|
+
tintClassName,
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<i className={`fa-light ${icon} text-[11px]`} aria-hidden="true" />
|
|
48
|
+
{label}
|
|
49
|
+
</Badge>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* List-page **dashboard** view — thin alias over {@link DashboardReportCharts}.
|
|
5
|
+
* Uses a single-row KPI strip (`metricsSingleRow`) for dense hubs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react"
|
|
9
|
+
import {
|
|
10
|
+
DashboardReportCharts,
|
|
11
|
+
type DashboardReportChartsProps,
|
|
12
|
+
} from "@/components/dashboard-report-charts"
|
|
13
|
+
|
|
14
|
+
export type ListPageDashboardChartsProps = Omit<DashboardReportChartsProps, "metricsSingleRow">
|
|
15
|
+
|
|
16
|
+
export function ListPageDashboardCharts(props: ListPageDashboardChartsProps) {
|
|
17
|
+
return <DashboardReportCharts {...props} metricsSingleRow />
|
|
18
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuSeparator,
|
|
8
|
+
DropdownMenuTrigger,
|
|
9
|
+
} from "@/components/ui/dropdown-menu"
|
|
10
|
+
import {
|
|
11
|
+
SidebarGroup,
|
|
12
|
+
SidebarGroupLabel,
|
|
13
|
+
SidebarMenu,
|
|
14
|
+
SidebarMenuAction,
|
|
15
|
+
SidebarMenuButton,
|
|
16
|
+
SidebarMenuItem,
|
|
17
|
+
useSidebar,
|
|
18
|
+
} from "@/components/ui/sidebar"
|
|
19
|
+
|
|
20
|
+
export function NavDocuments({
|
|
21
|
+
items,
|
|
22
|
+
}: {
|
|
23
|
+
items: {
|
|
24
|
+
name: string
|
|
25
|
+
url: string
|
|
26
|
+
icon: React.ReactNode
|
|
27
|
+
}[]
|
|
28
|
+
}) {
|
|
29
|
+
const { isMobile } = useSidebar()
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<SidebarGroup
|
|
33
|
+
className="group-data-[collapsible=icon]:hidden"
|
|
34
|
+
role="group"
|
|
35
|
+
aria-labelledby="nav-documents-section-label"
|
|
36
|
+
>
|
|
37
|
+
<SidebarGroupLabel id="nav-documents-section-label">Documents</SidebarGroupLabel>
|
|
38
|
+
<SidebarMenu>
|
|
39
|
+
{items.map((item) => (
|
|
40
|
+
<SidebarMenuItem key={item.name}>
|
|
41
|
+
<SidebarMenuButton asChild>
|
|
42
|
+
<a href={item.url}>
|
|
43
|
+
{item.icon}
|
|
44
|
+
<span>{item.name}</span>
|
|
45
|
+
</a>
|
|
46
|
+
</SidebarMenuButton>
|
|
47
|
+
<DropdownMenu>
|
|
48
|
+
<DropdownMenuTrigger asChild>
|
|
49
|
+
<SidebarMenuAction
|
|
50
|
+
showOnHover
|
|
51
|
+
suppressHydrationWarning
|
|
52
|
+
className="rounded-sm data-[state=open]:bg-accent"
|
|
53
|
+
>
|
|
54
|
+
<i className="fa-light fa-ellipsis" aria-hidden="true" />
|
|
55
|
+
<span className="sr-only">More</span>
|
|
56
|
+
</SidebarMenuAction>
|
|
57
|
+
</DropdownMenuTrigger>
|
|
58
|
+
<DropdownMenuContent
|
|
59
|
+
className="w-24 rounded-lg"
|
|
60
|
+
side={isMobile ? "bottom" : "right"}
|
|
61
|
+
align={isMobile ? "end" : "start"}
|
|
62
|
+
>
|
|
63
|
+
<DropdownMenuItem>
|
|
64
|
+
<i className="fa-light fa-folder" aria-hidden="true" />
|
|
65
|
+
<span>Open</span>
|
|
66
|
+
</DropdownMenuItem>
|
|
67
|
+
<DropdownMenuItem>
|
|
68
|
+
<i className="fa-light fa-share-nodes" aria-hidden="true" />
|
|
69
|
+
<span>Share</span>
|
|
70
|
+
</DropdownMenuItem>
|
|
71
|
+
<DropdownMenuSeparator />
|
|
72
|
+
<DropdownMenuItem variant="destructive">
|
|
73
|
+
<i className="fa-light fa-trash" aria-hidden="true" />
|
|
74
|
+
<span>Delete</span>
|
|
75
|
+
</DropdownMenuItem>
|
|
76
|
+
</DropdownMenuContent>
|
|
77
|
+
</DropdownMenu>
|
|
78
|
+
</SidebarMenuItem>
|
|
79
|
+
))}
|
|
80
|
+
<SidebarMenuItem>
|
|
81
|
+
<SidebarMenuButton className="text-sidebar-foreground/70">
|
|
82
|
+
<i className="fa-light fa-ellipsis text-sidebar-foreground/70" aria-hidden="true" />
|
|
83
|
+
<span>More</span>
|
|
84
|
+
</SidebarMenuButton>
|
|
85
|
+
</SidebarMenuItem>
|
|
86
|
+
</SidebarMenu>
|
|
87
|
+
</SidebarGroup>
|
|
88
|
+
)
|
|
89
|
+
}
|