@tollerud/ui 1.1.4 → 3.0.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/AGENTS.md +34 -11
- package/CHANGELOG.md +376 -0
- package/COMPONENTS.md +951 -0
- package/GETTING_STARTED.md +159 -0
- package/README.md +51 -43
- package/SKILL.md +55 -19
- package/components.json +18 -0
- package/dist/accordion.d.ts +20 -0
- package/dist/accordion.js +5 -0
- package/dist/accordion.js.map +1 -0
- package/dist/action-diff.d.ts +26 -0
- package/dist/action-diff.js +5 -0
- package/dist/action-diff.js.map +1 -0
- package/dist/action-row.d.ts +36 -0
- package/dist/action-row.js +6 -0
- package/dist/action-row.js.map +1 -0
- package/dist/alert-inbox.d.ts +23 -0
- package/dist/alert-inbox.js +6 -0
- package/dist/alert-inbox.js.map +1 -0
- package/dist/alert.d.ts +33 -0
- package/dist/alert.js +5 -0
- package/dist/alert.js.map +1 -0
- package/dist/approval-card.d.ts +27 -0
- package/dist/approval-card.js +5 -0
- package/dist/approval-card.js.map +1 -0
- package/dist/area-chart.d.ts +10 -0
- package/dist/area-chart.js +5 -0
- package/dist/area-chart.js.map +1 -0
- package/dist/avatar.d.ts +27 -0
- package/dist/avatar.js +5 -0
- package/dist/avatar.js.map +1 -0
- package/dist/backup-status-panel.d.ts +25 -0
- package/dist/backup-status-panel.js +6 -0
- package/dist/backup-status-panel.js.map +1 -0
- package/dist/badge.d.ts +17 -0
- package/dist/badge.js +5 -0
- package/dist/badge.js.map +1 -0
- package/dist/bar-chart.d.ts +15 -0
- package/dist/bar-chart.js +5 -0
- package/dist/bar-chart.js.map +1 -0
- package/dist/bento-dashboard.d.ts +30 -0
- package/dist/bento-dashboard.js +5 -0
- package/dist/bento-dashboard.js.map +1 -0
- package/dist/breadcrumb.d.ts +16 -0
- package/dist/breadcrumb.js +5 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.d.ts +29 -0
- package/dist/button.js +5 -0
- package/dist/button.js.map +1 -0
- package/dist/card.d.ts +10 -0
- package/dist/card.js +5 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.d.ts +9 -0
- package/dist/checkbox.js +5 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-2QWKOCWF.js +79 -0
- package/dist/chunk-2QWKOCWF.js.map +1 -0
- package/dist/chunk-3TGMGBKM.js +393 -0
- package/dist/chunk-3TGMGBKM.js.map +1 -0
- package/dist/chunk-3XTZPDNV.js +94 -0
- package/dist/chunk-3XTZPDNV.js.map +1 -0
- package/dist/chunk-435JHF7G.js +65 -0
- package/dist/chunk-435JHF7G.js.map +1 -0
- package/dist/chunk-4PA2ACNF.js +52 -0
- package/dist/chunk-4PA2ACNF.js.map +1 -0
- package/dist/chunk-5GWHUJ5D.js +29 -0
- package/dist/chunk-5GWHUJ5D.js.map +1 -0
- package/dist/chunk-6FUKJD3W.js +123 -0
- package/dist/chunk-6FUKJD3W.js.map +1 -0
- package/dist/chunk-6IS2AYYG.js +106 -0
- package/dist/chunk-6IS2AYYG.js.map +1 -0
- package/dist/chunk-6PZKU6ZL.js +78 -0
- package/dist/chunk-6PZKU6ZL.js.map +1 -0
- package/dist/chunk-6SKTH45H.js +75 -0
- package/dist/chunk-6SKTH45H.js.map +1 -0
- package/dist/chunk-6UXW5YUC.js +77 -0
- package/dist/chunk-6UXW5YUC.js.map +1 -0
- package/dist/chunk-7EP2T3OW.js +52 -0
- package/dist/chunk-7EP2T3OW.js.map +1 -0
- package/dist/chunk-7J5QXUQN.js +38 -0
- package/dist/chunk-7J5QXUQN.js.map +1 -0
- package/dist/chunk-7TOT5ME3.js +53 -0
- package/dist/chunk-7TOT5ME3.js.map +1 -0
- package/dist/chunk-A6L5C3IJ.js +47 -0
- package/dist/chunk-A6L5C3IJ.js.map +1 -0
- package/dist/chunk-ANW6J6PV.js +42 -0
- package/dist/chunk-ANW6J6PV.js.map +1 -0
- package/dist/chunk-APFFKNPS.js +80 -0
- package/dist/chunk-APFFKNPS.js.map +1 -0
- package/dist/chunk-AZADSX4Z.js +85 -0
- package/dist/chunk-AZADSX4Z.js.map +1 -0
- package/dist/chunk-BPCH5LJ3.js +36 -0
- package/dist/chunk-BPCH5LJ3.js.map +1 -0
- package/dist/chunk-CBQ63EBL.js +85 -0
- package/dist/chunk-CBQ63EBL.js.map +1 -0
- package/dist/chunk-CDI7353B.js +40 -0
- package/dist/chunk-CDI7353B.js.map +1 -0
- package/dist/chunk-CKNWXYMA.js +53 -0
- package/dist/chunk-CKNWXYMA.js.map +1 -0
- package/dist/chunk-DFM7UUKB.js +79 -0
- package/dist/chunk-DFM7UUKB.js.map +1 -0
- package/dist/chunk-DGCRHVWW.js +84 -0
- package/dist/chunk-DGCRHVWW.js.map +1 -0
- package/dist/chunk-DNJI65VQ.js +22 -0
- package/dist/chunk-DNJI65VQ.js.map +1 -0
- package/dist/chunk-DOUDJU4P.js +63 -0
- package/dist/chunk-DOUDJU4P.js.map +1 -0
- package/dist/chunk-DRCMGIQ6.js +64 -0
- package/dist/chunk-DRCMGIQ6.js.map +1 -0
- package/dist/chunk-DZOBXK2S.js +28 -0
- package/dist/chunk-DZOBXK2S.js.map +1 -0
- package/dist/chunk-EN4OJCEF.js +54 -0
- package/dist/chunk-EN4OJCEF.js.map +1 -0
- package/dist/chunk-EVHZFYWX.js +33 -0
- package/dist/chunk-EVHZFYWX.js.map +1 -0
- package/dist/chunk-FGXOV2QH.js +23 -0
- package/dist/chunk-FGXOV2QH.js.map +1 -0
- package/dist/chunk-G2VKWNZA.js +53 -0
- package/dist/chunk-G2VKWNZA.js.map +1 -0
- package/dist/chunk-GTM2DE4C.js +156 -0
- package/dist/chunk-GTM2DE4C.js.map +1 -0
- package/dist/chunk-H3ZVGTJM.js +165 -0
- package/dist/chunk-H3ZVGTJM.js.map +1 -0
- package/dist/chunk-HWAWUEHC.js +28 -0
- package/dist/chunk-HWAWUEHC.js.map +1 -0
- package/dist/chunk-HWJVRTWO.js +36 -0
- package/dist/chunk-HWJVRTWO.js.map +1 -0
- package/dist/chunk-HYQGOC2E.js +79 -0
- package/dist/chunk-HYQGOC2E.js.map +1 -0
- package/dist/chunk-ILADNTUB.js +77 -0
- package/dist/chunk-ILADNTUB.js.map +1 -0
- package/dist/chunk-ISHZ6ZPJ.js +31 -0
- package/dist/chunk-ISHZ6ZPJ.js.map +1 -0
- package/dist/chunk-JRFSUVSO.js +66 -0
- package/dist/chunk-JRFSUVSO.js.map +1 -0
- package/dist/chunk-KI6OTVID.js +91 -0
- package/dist/chunk-KI6OTVID.js.map +1 -0
- package/dist/chunk-LUM2YJBH.js +73 -0
- package/dist/chunk-LUM2YJBH.js.map +1 -0
- package/dist/chunk-NHPISZWS.js +71 -0
- package/dist/chunk-NHPISZWS.js.map +1 -0
- package/dist/chunk-NOLWJJHT.js +52 -0
- package/dist/chunk-NOLWJJHT.js.map +1 -0
- package/dist/chunk-NPVINX3Q.js +20 -0
- package/dist/chunk-NPVINX3Q.js.map +1 -0
- package/dist/chunk-NSMU66ZX.js +47 -0
- package/dist/chunk-NSMU66ZX.js.map +1 -0
- package/dist/chunk-O57QMLNI.js +68 -0
- package/dist/chunk-O57QMLNI.js.map +1 -0
- package/dist/chunk-O5SWPHUQ.js +79 -0
- package/dist/chunk-O5SWPHUQ.js.map +1 -0
- package/dist/chunk-OGVSZ7NV.js +53 -0
- package/dist/chunk-OGVSZ7NV.js.map +1 -0
- package/dist/chunk-OONIUDST.js +48 -0
- package/dist/chunk-OONIUDST.js.map +1 -0
- package/dist/chunk-PLF3BBQI.js +139 -0
- package/dist/chunk-PLF3BBQI.js.map +1 -0
- package/dist/chunk-Q74VRQEX.js +26 -0
- package/dist/chunk-Q74VRQEX.js.map +1 -0
- package/dist/chunk-QEHTPQHL.js +35 -0
- package/dist/chunk-QEHTPQHL.js.map +1 -0
- package/dist/chunk-RJTDQOT2.js +73 -0
- package/dist/chunk-RJTDQOT2.js.map +1 -0
- package/dist/chunk-RQ3RXKAZ.js +203 -0
- package/dist/chunk-RQ3RXKAZ.js.map +1 -0
- package/dist/chunk-RWJELAS6.js +46 -0
- package/dist/chunk-RWJELAS6.js.map +1 -0
- package/dist/chunk-RZK2S2OO.js +126 -0
- package/dist/chunk-RZK2S2OO.js.map +1 -0
- package/dist/chunk-SAP7JSSO.js +106 -0
- package/dist/chunk-SAP7JSSO.js.map +1 -0
- package/dist/chunk-T3TQPOVM.js +79 -0
- package/dist/chunk-T3TQPOVM.js.map +1 -0
- package/dist/chunk-T56TTOI6.js +53 -0
- package/dist/chunk-T56TTOI6.js.map +1 -0
- package/dist/chunk-T7EFDE2L.js +36 -0
- package/dist/chunk-T7EFDE2L.js.map +1 -0
- package/dist/chunk-V3P5QLLX.js +154 -0
- package/dist/chunk-V3P5QLLX.js.map +1 -0
- package/dist/chunk-VTRUUT5K.js +31 -0
- package/dist/chunk-VTRUUT5K.js.map +1 -0
- package/dist/chunk-WDANALHD.js +95 -0
- package/dist/chunk-WDANALHD.js.map +1 -0
- package/dist/chunk-WSQNPRGN.js +12 -0
- package/dist/chunk-WSQNPRGN.js.map +1 -0
- package/dist/chunk-XR5QBVEV.js +56 -0
- package/dist/chunk-XR5QBVEV.js.map +1 -0
- package/dist/chunk-YYWODLER.js +111 -0
- package/dist/chunk-YYWODLER.js.map +1 -0
- package/dist/chunk-ZOXO3G3I.js +50 -0
- package/dist/chunk-ZOXO3G3I.js.map +1 -0
- package/dist/code-block.d.ts +14 -0
- package/dist/code-block.js +5 -0
- package/dist/code-block.js.map +1 -0
- package/dist/combobox.d.ts +26 -0
- package/dist/combobox.js +5 -0
- package/dist/combobox.js.map +1 -0
- package/dist/command-menu.d.ts +52 -0
- package/dist/command-menu.js +7 -0
- package/dist/command-menu.js.map +1 -0
- package/dist/container.d.ts +9 -0
- package/dist/container.js +5 -0
- package/dist/container.js.map +1 -0
- package/dist/cta-band.d.ts +12 -0
- package/dist/cta-band.js +5 -0
- package/dist/cta-band.js.map +1 -0
- package/dist/data-table.d.ts +58 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-picker.d.ts +20 -0
- package/dist/date-picker.js +5 -0
- package/dist/date-picker.js.map +1 -0
- package/dist/dialog.d.ts +21 -0
- package/dist/dialog.js +5 -0
- package/dist/dialog.js.map +1 -0
- package/dist/divider.d.ts +12 -0
- package/dist/divider.js +5 -0
- package/dist/divider.js.map +1 -0
- package/dist/docker-stack-card.d.ts +21 -0
- package/dist/docker-stack-card.js +6 -0
- package/dist/docker-stack-card.js.map +1 -0
- package/dist/donut.d.ts +15 -0
- package/dist/donut.js +5 -0
- package/dist/donut.js.map +1 -0
- package/dist/dropdown-menu.d.ts +15 -0
- package/dist/dropdown-menu.js +5 -0
- package/dist/dropdown-menu.js.map +1 -0
- package/dist/empty.d.ts +12 -0
- package/dist/empty.js +5 -0
- package/dist/empty.js.map +1 -0
- package/dist/feature-card.d.ts +11 -0
- package/dist/feature-card.js +6 -0
- package/dist/feature-card.js.map +1 -0
- package/dist/file-upload.d.ts +20 -0
- package/dist/file-upload.js +5 -0
- package/dist/file-upload.js.map +1 -0
- package/dist/footer.d.ts +35 -0
- package/dist/footer.js +5 -0
- package/dist/footer.js.map +1 -0
- package/dist/form-row.d.ts +15 -0
- package/dist/form-row.js +5 -0
- package/dist/form-row.js.map +1 -0
- package/dist/glow-card.d.ts +14 -0
- package/dist/glow-card.js +5 -0
- package/dist/glow-card.js.map +1 -0
- package/dist/hero-block.d.ts +16 -0
- package/dist/hero-block.js +7 -0
- package/dist/hero-block.js.map +1 -0
- package/dist/host-card.d.ts +27 -0
- package/dist/host-card.js +6 -0
- package/dist/host-card.js.map +1 -0
- package/dist/incident-card.d.ts +23 -0
- package/dist/incident-card.js +5 -0
- package/dist/incident-card.js.map +1 -0
- package/dist/index.d.ts +76 -960
- package/dist/index.js +68 -3812
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +10 -0
- package/dist/input.js +5 -0
- package/dist/input.js.map +1 -0
- package/dist/kbd.d.ts +24 -0
- package/dist/kbd.js +5 -0
- package/dist/kbd.js.map +1 -0
- package/dist/log-viewer.d.ts +35 -0
- package/dist/log-viewer.js +5 -0
- package/dist/log-viewer.js.map +1 -0
- package/dist/meter.d.ts +23 -0
- package/dist/meter.js +5 -0
- package/dist/meter.js.map +1 -0
- package/dist/noir-glow-background.d.ts +50 -0
- package/dist/noir-glow-background.js +4 -0
- package/dist/noir-glow-background.js.map +1 -0
- package/dist/pagination.d.ts +16 -0
- package/dist/pagination.js +5 -0
- package/dist/pagination.js.map +1 -0
- package/dist/panel.d.ts +12 -0
- package/dist/panel.js +5 -0
- package/dist/panel.js.map +1 -0
- package/dist/password-input.d.ts +10 -0
- package/dist/password-input.js +5 -0
- package/dist/password-input.js.map +1 -0
- package/dist/pill.d.ts +14 -0
- package/dist/pill.js +5 -0
- package/dist/pill.js.map +1 -0
- package/dist/pricing-card.d.ts +20 -0
- package/dist/pricing-card.js +6 -0
- package/dist/pricing-card.js.map +1 -0
- package/dist/progress.d.ts +6 -0
- package/dist/progress.js +5 -0
- package/dist/progress.js.map +1 -0
- package/dist/radio-group.d.ts +18 -0
- package/dist/radio-group.js +5 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/rollback-plan.d.ts +23 -0
- package/dist/rollback-plan.js +5 -0
- package/dist/rollback-plan.js.map +1 -0
- package/dist/segmented.d.ts +17 -0
- package/dist/segmented.js +5 -0
- package/dist/segmented.js.map +1 -0
- package/dist/select.d.ts +18 -0
- package/dist/select.js +5 -0
- package/dist/select.js.map +1 -0
- package/dist/service-health-card.d.ts +21 -0
- package/dist/service-health-card.js +6 -0
- package/dist/service-health-card.js.map +1 -0
- package/dist/sheet.d.ts +25 -0
- package/dist/sheet.js +5 -0
- package/dist/sheet.js.map +1 -0
- package/dist/skeleton.d.ts +5 -0
- package/dist/skeleton.js +5 -0
- package/dist/skeleton.js.map +1 -0
- package/dist/slider.d.ts +12 -0
- package/dist/slider.js +5 -0
- package/dist/slider.js.map +1 -0
- package/dist/sparkline.d.ts +16 -0
- package/dist/sparkline.js +5 -0
- package/dist/sparkline.js.map +1 -0
- package/dist/stat-card.d.ts +15 -0
- package/dist/stat-card.js +5 -0
- package/dist/stat-card.js.map +1 -0
- package/dist/status-dot.d.ts +13 -0
- package/dist/status-dot.js +5 -0
- package/dist/status-dot.js.map +1 -0
- package/dist/stepper.d.ts +16 -0
- package/dist/stepper.js +5 -0
- package/dist/stepper.js.map +1 -0
- package/dist/switch.d.ts +9 -0
- package/dist/switch.js +5 -0
- package/dist/switch.js.map +1 -0
- package/dist/tabs.d.ts +9 -0
- package/dist/tabs.js +5 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag-input.d.ts +20 -0
- package/dist/tag-input.js +5 -0
- package/dist/tag-input.js.map +1 -0
- package/dist/textarea.d.ts +10 -0
- package/dist/textarea.js +5 -0
- package/dist/textarea.js.map +1 -0
- package/dist/timeline.d.ts +30 -0
- package/dist/timeline.js +5 -0
- package/dist/timeline.js.map +1 -0
- package/dist/toaster.d.ts +10 -0
- package/dist/toaster.js +4 -0
- package/dist/toaster.js.map +1 -0
- package/dist/tooltip.d.ts +12 -0
- package/dist/tooltip.js +5 -0
- package/dist/tooltip.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -0
- package/globals-layers.css +935 -0
- package/globals-v3.css +17 -0
- package/globals-v4.css +2 -0
- package/globals.css +12 -939
- package/package.json +82 -17
- package/registry.json +920 -0
- package/tailwind.css +9 -0
- package/tollerud-preset.js +55 -50
- package/dist/index.cjs +0 -3938
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -960
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var severityStyles = {
|
|
7
|
+
critical: { border: "border-tollerud-error/50", dot: "bg-tollerud-error shadow-[0_0_8px_rgba(239,68,68,0.6)]", label: "text-tollerud-error" },
|
|
8
|
+
high: { border: "border-tollerud-yellow/50", dot: "bg-tollerud-yellow shadow-[0_0_8px_rgba(232,213,0,0.5)]", label: "text-tollerud-yellow" },
|
|
9
|
+
medium: { border: "border-tollerud-amber/40", dot: "bg-tollerud-amber", label: "text-tollerud-amber" },
|
|
10
|
+
low: { border: "border-tollerud-noir-500", dot: "bg-tollerud-noir-400", label: "text-tollerud-text-muted" },
|
|
11
|
+
info: { border: "border-tollerud-info/30", dot: "bg-tollerud-info", label: "text-tollerud-info" }
|
|
12
|
+
};
|
|
13
|
+
var IncidentCard = forwardRef(
|
|
14
|
+
({ className, title, severity, timestamp, description, service, acknowledged, loading, ...props }, ref) => {
|
|
15
|
+
const style = severityStyles[severity];
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
ref,
|
|
20
|
+
className: cn(
|
|
21
|
+
"rounded-lg border bg-tollerud-surface-raised p-4",
|
|
22
|
+
"transition-[border-color] duration-[150ms]",
|
|
23
|
+
style.border,
|
|
24
|
+
acknowledged && "opacity-50",
|
|
25
|
+
loading && "animate-pulse",
|
|
26
|
+
className
|
|
27
|
+
),
|
|
28
|
+
...props,
|
|
29
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: cn("w-2 h-2 rounded-full mt-1.5 flex-shrink-0", style.dot) }),
|
|
31
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-tollerud-foreground truncate", children: title }),
|
|
34
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-[11px] font-medium uppercase whitespace-nowrap", style.label), children: severity })
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5 text-xs text-tollerud-text-muted", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("span", { children: timestamp }),
|
|
38
|
+
service && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx("span", { className: "text-tollerud-noir-500", children: "\xB7" }),
|
|
40
|
+
/* @__PURE__ */ jsx("span", { children: service })
|
|
41
|
+
] })
|
|
42
|
+
] }),
|
|
43
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-xs text-tollerud-text-secondary leading-relaxed", children: description })
|
|
44
|
+
] })
|
|
45
|
+
] })
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
IncidentCard.displayName = "IncidentCard";
|
|
51
|
+
|
|
52
|
+
export { IncidentCard };
|
|
53
|
+
//# sourceMappingURL=chunk-EN4OJCEF.js.map
|
|
54
|
+
//# sourceMappingURL=chunk-EN4OJCEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/IncidentCard.tsx"],"names":[],"mappings":";;;;AAsBA,IAAM,cAAA,GAA2F;AAAA,EAC/F,UAAU,EAAE,MAAA,EAAQ,4BAA4B,GAAA,EAAK,wDAAA,EAA0D,OAAO,qBAAA,EAAsB;AAAA,EAC5I,MAAU,EAAE,MAAA,EAAQ,6BAA6B,GAAA,EAAK,yDAAA,EAA2D,OAAO,sBAAA,EAAuB;AAAA,EAC/I,QAAU,EAAE,MAAA,EAAQ,4BAA4B,GAAA,EAAK,mBAAA,EAAqB,OAAO,qBAAA,EAAsB;AAAA,EACvG,KAAU,EAAE,MAAA,EAAQ,4BAA4B,GAAA,EAAK,sBAAA,EAAwB,OAAO,0BAAA,EAA2B;AAAA,EAC/G,MAAU,EAAE,MAAA,EAAQ,2BAA2B,GAAA,EAAK,kBAAA,EAAoB,OAAO,oBAAA;AACjF,CAAA;AAEA,IAAM,YAAA,GAAe,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,KAAA,EAAO,QAAA,EAAU,SAAA,EAAW,WAAA,EAAa,OAAA,EAAS,YAAA,EAAc,OAAA,EAAS,GAAG,KAAA,IAAS,GAAA,KAAQ;AACzG,IAAA,MAAM,KAAA,GAAQ,eAAe,QAAQ,CAAA;AAErC,IAAA,uBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,4CAAA;AAAA,UACA,KAAA,CAAM,MAAA;AAAA,UACN,YAAA,IAAgB,YAAA;AAAA,UAChB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,UAAK,SAAA,EAAW,EAAA,CAAG,2CAAA,EAA6C,KAAA,CAAM,GAAG,CAAA,EAAG,CAAA;AAAA,0BAC7E,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,yCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EAA2D,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,8BACjF,GAAA,CAAC,UAAK,SAAA,EAAW,EAAA,CAAG,uDAAuD,KAAA,CAAM,KAAK,GACnF,QAAA,EAAA,QAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,4BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iEAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,UAAM,QAAA,EAAA,SAAA,EAAU,CAAA;AAAA,cAChB,2BAAW,IAAA,CAAA,QAAA,EAAA,EAAE,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EAAyB,QAAA,EAAA,MAAA,EAAC,CAAA;AAAA,gCAAO,GAAA,CAAC,UAAM,QAAA,EAAA,OAAA,EAAQ;AAAA,eAAA,EAAO;AAAA,aAAA,EACvF,CAAA;AAAA,YACC,WAAA,oBACC,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,+DAA+D,QAAA,EAAA,WAAA,EAAY;AAAA,WAAA,EAE5F;AAAA,SAAA,EACF;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,YAAA,CAAa,WAAA,GAAc,cAAA","file":"chunk-EN4OJCEF.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type IncidentSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info'\n\nexport interface IncidentCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Incident title */\n title: string\n /** Severity level */\n severity: IncidentSeverity\n /** Timestamp string */\n timestamp: string\n /** Description / detail */\n description?: string\n /** Service name affected */\n service?: string\n /** Whether acknowledged */\n acknowledged?: boolean\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst severityStyles: Record<IncidentSeverity, { border: string; dot: string; label: string }> = {\n critical: { border: 'border-tollerud-error/50', dot: 'bg-tollerud-error shadow-[0_0_8px_rgba(239,68,68,0.6)]', label: 'text-tollerud-error' },\n high: { border: 'border-tollerud-yellow/50', dot: 'bg-tollerud-yellow shadow-[0_0_8px_rgba(232,213,0,0.5)]', label: 'text-tollerud-yellow' },\n medium: { border: 'border-tollerud-amber/40', dot: 'bg-tollerud-amber', label: 'text-tollerud-amber' },\n low: { border: 'border-tollerud-noir-500', dot: 'bg-tollerud-noir-400', label: 'text-tollerud-text-muted' },\n info: { border: 'border-tollerud-info/30', dot: 'bg-tollerud-info', label: 'text-tollerud-info' },\n}\n\nconst IncidentCard = forwardRef<HTMLDivElement, IncidentCardProps>(\n ({ className, title, severity, timestamp, description, service, acknowledged, loading, ...props }, ref) => {\n const style = severityStyles[severity]\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-[150ms]',\n style.border,\n acknowledged && 'opacity-50',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-start gap-3\">\n <span className={cn('w-2 h-2 rounded-full mt-1.5 flex-shrink-0', style.dot)} />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-semibold text-tollerud-foreground truncate\">{title}</span>\n <span className={cn('text-[11px] font-medium uppercase whitespace-nowrap', style.label)}>\n {severity}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-0.5 text-xs text-tollerud-text-muted\">\n <span>{timestamp}</span>\n {service && <><span className=\"text-tollerud-noir-500\">·</span><span>{service}</span></>}\n </div>\n {description && (\n <p className=\"mt-1.5 text-xs text-tollerud-text-secondary leading-relaxed\">{description}</p>\n )}\n </div>\n </div>\n </div>\n )\n }\n)\nIncidentCard.displayName = 'IncidentCard'\n\nexport { IncidentCard }\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Sparkline = forwardRef(
|
|
7
|
+
({ className, data, width, height, w, h, color = "#E8D500", ...props }, ref) => {
|
|
8
|
+
const resolvedWidth = width ?? w ?? 120;
|
|
9
|
+
const resolvedHeight = height ?? h ?? 34;
|
|
10
|
+
const max = Math.max(...data);
|
|
11
|
+
const min = Math.min(...data);
|
|
12
|
+
const span = Math.max(data.length - 1, 1);
|
|
13
|
+
const pts = data.map(
|
|
14
|
+
(v, i) => `${i / span * resolvedWidth},${resolvedHeight - 2 - (v - min) / (max - min || 1) * (resolvedHeight - 4)}`
|
|
15
|
+
).join(" ");
|
|
16
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cn("inline-block", className), ...props, children: /* @__PURE__ */ jsx("svg", { width: resolvedWidth, height: resolvedHeight, className: "block", role: "img", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
17
|
+
"polyline",
|
|
18
|
+
{
|
|
19
|
+
points: pts,
|
|
20
|
+
fill: "none",
|
|
21
|
+
stroke: color,
|
|
22
|
+
strokeWidth: "1.6",
|
|
23
|
+
strokeLinecap: "round",
|
|
24
|
+
strokeLinejoin: "round"
|
|
25
|
+
}
|
|
26
|
+
) }) });
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
Sparkline.displayName = "Sparkline";
|
|
30
|
+
|
|
31
|
+
export { Sparkline };
|
|
32
|
+
//# sourceMappingURL=chunk-EVHZFYWX.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-EVHZFYWX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/Sparkline.tsx"],"names":[],"mappings":";;;;AAcA,IAAM,SAAA,GAAY,UAAA;AAAA,EAChB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,CAAA,EAAG,CAAA,EAAG,KAAA,GAAQ,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,KAAQ;AAC9E,IAAA,MAAM,aAAA,GAAgB,SAAS,CAAA,IAAK,GAAA;AACpC,IAAA,MAAM,cAAA,GAAiB,UAAU,CAAA,IAAK,EAAA;AACtC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAI,CAAA;AAC5B,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,GAAG,IAAI,CAAA;AAC5B,IAAA,MAAM,OAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,MAAA,GAAS,GAAG,CAAC,CAAA;AACxC,IAAA,MAAM,MAAM,IAAA,CACT,GAAA;AAAA,MACC,CAAC,CAAA,EAAG,CAAA,KACF,CAAA,EAAI,CAAA,GAAI,OAAQ,aAAa,CAAA,CAAA,EAAI,cAAA,GAAiB,CAAA,GAAA,CAAM,IAAI,GAAA,KAAQ,GAAA,GAAM,GAAA,IAAO,CAAA,CAAA,IAAO,iBAAiB,CAAA,CAAE,CAAA;AAAA,KAC/G,CACC,KAAK,GAAG,CAAA;AAEX,IAAA,uBACE,GAAA,CAAC,SAAI,GAAA,EAAU,SAAA,EAAW,GAAG,cAAA,EAAgB,SAAS,GAAI,GAAG,KAAA,EAC3D,8BAAC,KAAA,EAAA,EAAI,KAAA,EAAO,eAAe,MAAA,EAAQ,cAAA,EAAgB,WAAU,OAAA,EAAQ,IAAA,EAAK,KAAA,EAAM,aAAA,EAAY,MAAA,EAC1F,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,MAAA,EAAQ,GAAA;AAAA,QACR,IAAA,EAAK,MAAA;AAAA,QACL,MAAA,EAAQ,KAAA;AAAA,QACR,WAAA,EAAY,KAAA;AAAA,QACZ,aAAA,EAAc,OAAA;AAAA,QACd,cAAA,EAAe;AAAA;AAAA,OAEnB,CAAA,EACF,CAAA;AAAA,EAEJ;AACF;AACA,SAAA,CAAU,WAAA,GAAc,WAAA","file":"chunk-EVHZFYWX.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface SparklineProps extends HTMLAttributes<HTMLDivElement> {\n data: number[]\n width?: number\n height?: number\n /** @deprecated Use `width` */\n w?: number\n /** @deprecated Use `height` */\n h?: number\n color?: string\n}\n\nconst Sparkline = forwardRef<HTMLDivElement, SparklineProps>(\n ({ className, data, width, height, w, h, color = '#E8D500', ...props }, ref) => {\n const resolvedWidth = width ?? w ?? 120\n const resolvedHeight = height ?? h ?? 34\n const max = Math.max(...data)\n const min = Math.min(...data)\n const span = Math.max(data.length - 1, 1)\n const pts = data\n .map(\n (v, i) =>\n `${(i / span) * resolvedWidth},${resolvedHeight - 2 - ((v - min) / (max - min || 1)) * (resolvedHeight - 4)}`\n )\n .join(' ')\n\n return (\n <div ref={ref} className={cn('inline-block', className)} {...props}>\n <svg width={resolvedWidth} height={resolvedHeight} className=\"block\" role=\"img\" aria-hidden=\"true\">\n <polyline\n points={pts}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </div>\n )\n }\n)\nSparkline.displayName = 'Sparkline'\n\nexport { Sparkline }\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function Skeleton({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}) {
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"div",
|
|
11
|
+
{
|
|
12
|
+
className: cn(
|
|
13
|
+
"animate-pulse rounded-md bg-tollerud-noir-800",
|
|
14
|
+
className
|
|
15
|
+
),
|
|
16
|
+
...props
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Skeleton };
|
|
22
|
+
//# sourceMappingURL=chunk-FGXOV2QH.js.map
|
|
23
|
+
//# sourceMappingURL=chunk-FGXOV2QH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/Skeleton.tsx"],"names":[],"mappings":";;;AAEA,SAAS,QAAA,CAAS;AAAA,EAChB,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAyC;AACvC,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,EAAA;AAAA,QACT,+CAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN;AAEJ","file":"chunk-FGXOV2QH.js","sourcesContent":["import { cn } from '@/lib/utils'\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\n 'animate-pulse rounded-md bg-tollerud-noir-800',\n className\n )}\n {...props}\n />\n )\n}\n\nexport { Skeleton }"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { StatusDot } from './chunk-NHPISZWS.js';
|
|
3
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
4
|
+
import { forwardRef } from 'react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var DockerStackCard = forwardRef(
|
|
8
|
+
({ className, name, services, composePath, loading, ...props }, ref) => {
|
|
9
|
+
const onlineCount = services.filter((s) => s.status === "online").length;
|
|
10
|
+
const degraded = services.some((s) => s.status === "offline" || s.status === "warning");
|
|
11
|
+
const status = services.every((s) => s.status === "online") ? "online" : degraded ? "warning" : "offline";
|
|
12
|
+
return /* @__PURE__ */ jsxs(
|
|
13
|
+
"div",
|
|
14
|
+
{
|
|
15
|
+
ref,
|
|
16
|
+
className: cn(
|
|
17
|
+
"rounded-lg border bg-tollerud-surface-raised p-4",
|
|
18
|
+
"transition-[border-color] duration-[150ms]",
|
|
19
|
+
status === "offline" && "border-tollerud-error/40",
|
|
20
|
+
status === "warning" && "border-tollerud-yellow/30",
|
|
21
|
+
status === "online" && "border-tollerud-border hover:border-tollerud-noir-500",
|
|
22
|
+
loading && "animate-pulse",
|
|
23
|
+
className
|
|
24
|
+
),
|
|
25
|
+
...props,
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
28
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
29
|
+
/* @__PURE__ */ jsx(StatusDot, { status }),
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-tollerud-foreground truncate", children: name })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-tollerud-text-muted whitespace-nowrap ml-2", children: [
|
|
33
|
+
onlineCount,
|
|
34
|
+
"/",
|
|
35
|
+
services.length,
|
|
36
|
+
" healthy"
|
|
37
|
+
] })
|
|
38
|
+
] }),
|
|
39
|
+
composePath && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-tollerud-text-muted font-mono mb-2 truncate", children: composePath }),
|
|
40
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: services.map((svc) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary truncate", children: svc.name }),
|
|
42
|
+
/* @__PURE__ */ jsx(StatusDot, { status: svc.status })
|
|
43
|
+
] }, svc.name)) })
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
DockerStackCard.displayName = "DockerStackCard";
|
|
50
|
+
|
|
51
|
+
export { DockerStackCard };
|
|
52
|
+
//# sourceMappingURL=chunk-G2VKWNZA.js.map
|
|
53
|
+
//# sourceMappingURL=chunk-G2VKWNZA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/DockerStackCard.tsx"],"names":[],"mappings":";;;;;AAoBA,IAAM,eAAA,GAAkB,UAAA;AAAA,EACtB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,QAAA,EAAU,aAAa,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACtE,IAAA,MAAM,WAAA,GAAc,SAAS,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,CAAE,MAAA;AAClE,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAA,KAAW,SAAA,IAAa,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA;AACtF,IAAA,MAAM,MAAA,GAAiB,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,GAAI,QAAA,GAAW,QAAA,GAAW,SAAA,GAAY,SAAA;AAExG,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,4CAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,aAAU,MAAA,EAAgB,CAAA;AAAA,8BAC3B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,IAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,4BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA;AAAA,cAAA,WAAA;AAAA,cAAY,GAAA;AAAA,cAAE,QAAA,CAAS,MAAA;AAAA,cAAO;AAAA,aAAA,EACjC;AAAA,WAAA,EACF,CAAA;AAAA,UACC,WAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gEACZ,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,0BAEF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,qBACb,IAAA,CAAC,KAAA,EAAA,EAAmB,SAAA,EAAU,2CAAA,EAC5B,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAyC,QAAA,EAAA,GAAA,CAAI,IAAA,EAAK,CAAA;AAAA,4BAClE,GAAA,CAAC,SAAA,EAAA,EAAU,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ;AAAA,WAAA,EAAA,EAFvB,GAAA,CAAI,IAGd,CACD,CAAA,EACH;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,eAAA,CAAgB,WAAA,GAAc,iBAAA","file":"chunk-G2VKWNZA.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface StackService {\n name: string\n status: Status\n}\n\nexport interface DockerStackCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Stack name */\n name: string\n /** Services in the stack */\n services: StackService[]\n /** Path to compose file */\n composePath?: string\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst DockerStackCard = forwardRef<HTMLDivElement, DockerStackCardProps>(\n ({ className, name, services, composePath, loading, ...props }, ref) => {\n const onlineCount = services.filter((s) => s.status === 'online').length\n const degraded = services.some((s) => s.status === 'offline' || s.status === 'warning')\n const status: Status = services.every((s) => s.status === 'online') ? 'online' : degraded ? 'warning' : 'offline'\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-[150ms]',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <StatusDot status={status} />\n <span className=\"font-semibold text-sm text-tollerud-foreground truncate\">\n {name}\n </span>\n </div>\n <span className=\"text-xs text-tollerud-text-muted whitespace-nowrap ml-2\">\n {onlineCount}/{services.length} healthy\n </span>\n </div>\n {composePath && (\n <div className=\"text-[11px] text-tollerud-text-muted font-mono mb-2 truncate\">\n {composePath}\n </div>\n )}\n <div className=\"flex flex-col gap-1\">\n {services.map((svc) => (\n <div key={svc.name} className=\"flex items-center justify-between text-xs\">\n <span className=\"text-tollerud-text-secondary truncate\">{svc.name}</span>\n <StatusDot status={svc.status} />\n </div>\n ))}\n </div>\n </div>\n )\n }\n)\nDockerStackCard.displayName = 'DockerStackCard'\n\nexport { DockerStackCard }\n"]}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { forwardRef, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Select = forwardRef(
|
|
7
|
+
({ className, label, error, placeholder, options = [], value, onChange, ...props }, ref) => {
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [highlightedIdx, setHighlightedIdx] = useState(0);
|
|
10
|
+
const containerRef = useRef(null);
|
|
11
|
+
const listRef = useRef(null);
|
|
12
|
+
const selectedOption = options.find((o) => o.value === value);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!open) return;
|
|
15
|
+
const handleClick = (e) => {
|
|
16
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
17
|
+
setOpen(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener("mousedown", handleClick);
|
|
21
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
22
|
+
}, [open]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (open) {
|
|
25
|
+
const idx = value ? options.findIndex((o) => o.value === value) : -1;
|
|
26
|
+
setHighlightedIdx(idx >= 0 ? idx : 0);
|
|
27
|
+
}
|
|
28
|
+
}, [open, options, value]);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (open && listRef.current) {
|
|
31
|
+
const item = listRef.current.children[highlightedIdx];
|
|
32
|
+
item?.scrollIntoView({ block: "nearest" });
|
|
33
|
+
}
|
|
34
|
+
}, [open, highlightedIdx]);
|
|
35
|
+
const selectOption = useCallback(
|
|
36
|
+
(opt) => {
|
|
37
|
+
onChange?.(opt.value);
|
|
38
|
+
setOpen(false);
|
|
39
|
+
},
|
|
40
|
+
[onChange]
|
|
41
|
+
);
|
|
42
|
+
const handleKeyDown = (e) => {
|
|
43
|
+
if (!open) {
|
|
44
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
setOpen(true);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
switch (e.key) {
|
|
51
|
+
case "Escape":
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
setOpen(false);
|
|
54
|
+
break;
|
|
55
|
+
case "ArrowDown":
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1));
|
|
58
|
+
break;
|
|
59
|
+
case "ArrowUp":
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
setHighlightedIdx((prev) => Math.max(prev - 1, 0));
|
|
62
|
+
break;
|
|
63
|
+
case "Enter":
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (options[highlightedIdx]) {
|
|
66
|
+
selectOption(options[highlightedIdx]);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", ref, ...props, children: [
|
|
72
|
+
label && /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-tollerud-text-muted", children: label }),
|
|
73
|
+
/* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative", children: [
|
|
74
|
+
/* @__PURE__ */ jsxs(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
onClick: () => setOpen(!open),
|
|
79
|
+
onKeyDown: handleKeyDown,
|
|
80
|
+
"aria-haspopup": "listbox",
|
|
81
|
+
"aria-expanded": open,
|
|
82
|
+
className: cn(
|
|
83
|
+
"font-sans text-sm w-full flex items-center justify-between px-3 py-2.5 rounded-lg",
|
|
84
|
+
"bg-tollerud-surface-raised",
|
|
85
|
+
"text-tollerud-text-primary text-left",
|
|
86
|
+
"transition-all duration-150 ease-out cursor-pointer",
|
|
87
|
+
error ? "border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_#EF4444]" : "border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]",
|
|
88
|
+
"border hover:border-tollerud-noir-400",
|
|
89
|
+
"focus:outline-none",
|
|
90
|
+
className
|
|
91
|
+
),
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ jsx("span", { className: cn(!selectedOption && "text-tollerud-text-muted"), children: selectedOption ? selectedOption.label : placeholder || "Select\u2026" }),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"svg",
|
|
96
|
+
{
|
|
97
|
+
className: cn(
|
|
98
|
+
"h-4 w-4 text-tollerud-text-muted transition-transform duration-150 flex-shrink-0",
|
|
99
|
+
open && "rotate-180"
|
|
100
|
+
),
|
|
101
|
+
fill: "none",
|
|
102
|
+
viewBox: "0 0 24 24",
|
|
103
|
+
stroke: "currentColor",
|
|
104
|
+
strokeWidth: 2,
|
|
105
|
+
"aria-hidden": "true",
|
|
106
|
+
children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 9l6 6 6-6" })
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
open && /* @__PURE__ */ jsxs(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
ref: listRef,
|
|
116
|
+
role: "listbox",
|
|
117
|
+
className: cn(
|
|
118
|
+
"absolute z-10 left-0 right-0 mt-1 py-1",
|
|
119
|
+
"rounded-lg border border-tollerud-border bg-tollerud-surface-overlay",
|
|
120
|
+
"shadow-[0_8px_24px_rgba(0,0,0,0.4)]",
|
|
121
|
+
"max-h-60 overflow-y-auto"
|
|
122
|
+
),
|
|
123
|
+
children: [
|
|
124
|
+
options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-tollerud-text-muted text-center", children: "No options" }),
|
|
125
|
+
options.map((opt, idx) => /* @__PURE__ */ jsx(
|
|
126
|
+
"button",
|
|
127
|
+
{
|
|
128
|
+
type: "button",
|
|
129
|
+
role: "option",
|
|
130
|
+
"aria-selected": opt.value === value,
|
|
131
|
+
onClick: () => selectOption(opt),
|
|
132
|
+
onMouseEnter: () => setHighlightedIdx(idx),
|
|
133
|
+
className: cn(
|
|
134
|
+
"w-full text-sm text-left px-3 py-2 transition-colors duration-75",
|
|
135
|
+
"cursor-pointer",
|
|
136
|
+
opt.value === value ? "text-tollerud-yellow" : "text-tollerud-text-primary",
|
|
137
|
+
idx === highlightedIdx && !(opt.value === value) ? "bg-tollerud-noir-700" : "hover:bg-tollerud-noir-700/60",
|
|
138
|
+
opt.value === value && highlightedIdx === idx && "bg-tollerud-noir-700"
|
|
139
|
+
),
|
|
140
|
+
children: opt.label
|
|
141
|
+
},
|
|
142
|
+
opt.value
|
|
143
|
+
))
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
] }),
|
|
148
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
|
|
149
|
+
] });
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
Select.displayName = "Select";
|
|
153
|
+
|
|
154
|
+
export { Select };
|
|
155
|
+
//# sourceMappingURL=chunk-GTM2DE4C.js.map
|
|
156
|
+
//# sourceMappingURL=chunk-GTM2DE4C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/Select.tsx"],"names":[],"mappings":"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","file":"chunk-GTM2DE4C.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, forwardRef, useState, useRef, useEffect, useCallback } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface SelectOption {\n value: string\n label: string\n}\n\nexport interface SelectProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n label?: string\n error?: string\n placeholder?: string\n options?: SelectOption[]\n value?: string\n onChange?: (value: string) => void\n}\n\nconst Select = forwardRef<HTMLDivElement, SelectProps>(\n ({ className, label, error, placeholder, options = [], value, onChange, ...props }, ref) => {\n const [open, setOpen] = useState(false)\n const [highlightedIdx, setHighlightedIdx] = useState(0)\n const containerRef = useRef<HTMLDivElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = options.find((o) => o.value === value)\n\n // Close on click outside\n useEffect(() => {\n if (!open) return\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false)\n }\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Reset highlight when opening\n useEffect(() => {\n if (open) {\n const idx = value ? options.findIndex((o) => o.value === value) : -1\n setHighlightedIdx(idx >= 0 ? idx : 0)\n }\n }, [open, options, value])\n\n // Scroll highlighted option into view\n useEffect(() => {\n if (open && listRef.current) {\n const item = listRef.current.children[highlightedIdx] as HTMLElement | undefined\n item?.scrollIntoView({ block: 'nearest' })\n }\n }, [open, highlightedIdx])\n\n const selectOption = useCallback(\n (opt: SelectOption) => {\n onChange?.(opt.value)\n setOpen(false)\n },\n [onChange]\n )\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n }\n return\n }\n\n switch (e.key) {\n case 'Escape':\n e.preventDefault()\n setOpen(false)\n break\n case 'ArrowDown':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.max(prev - 1, 0))\n break\n case 'Enter':\n e.preventDefault()\n if (options[highlightedIdx]) {\n selectOption(options[highlightedIdx])\n }\n break\n }\n }\n\n return (\n <div className=\"flex flex-col gap-1.5\" ref={ref} {...props}>\n {label && (\n <label className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n </label>\n )}\n <div ref={containerRef} className=\"relative\">\n {/* Trigger */}\n <button\n type=\"button\"\n onClick={() => setOpen(!open)}\n onKeyDown={handleKeyDown}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className={cn(\n 'font-sans text-sm w-full flex items-center justify-between px-3 py-2.5 rounded-lg',\n 'bg-tollerud-surface-raised',\n 'text-tollerud-text-primary text-left',\n 'transition-all duration-150 ease-out cursor-pointer',\n error\n ? 'border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_#EF4444]'\n : 'border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]',\n 'border hover:border-tollerud-noir-400',\n 'focus:outline-none',\n className\n )}\n >\n <span className={cn(!selectedOption && 'text-tollerud-text-muted')}>\n {selectedOption ? selectedOption.label : placeholder || 'Select…'}\n </span>\n <svg\n className={cn(\n 'h-4 w-4 text-tollerud-text-muted transition-transform duration-150 flex-shrink-0',\n open && 'rotate-180'\n )}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n aria-hidden=\"true\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 9l6 6 6-6\" />\n </svg>\n </button>\n\n {/* Dropdown */}\n {open && (\n <div\n ref={listRef}\n role=\"listbox\"\n className={cn(\n 'absolute z-10 left-0 right-0 mt-1 py-1',\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-overlay',\n 'shadow-[0_8px_24px_rgba(0,0,0,0.4)]',\n 'max-h-60 overflow-y-auto'\n )}\n >\n {options.length === 0 && (\n <div className=\"px-3 py-2 text-xs text-tollerud-text-muted text-center\">\n No options\n </div>\n )}\n {options.map((opt, idx) => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={opt.value === value}\n onClick={() => selectOption(opt)}\n onMouseEnter={() => setHighlightedIdx(idx)}\n className={cn(\n 'w-full text-sm text-left px-3 py-2 transition-colors duration-75',\n 'cursor-pointer',\n opt.value === value\n ? 'text-tollerud-yellow'\n : 'text-tollerud-text-primary',\n idx === highlightedIdx && !(opt.value === value)\n ? 'bg-tollerud-noir-700'\n : 'hover:bg-tollerud-noir-700/60',\n opt.value === value && highlightedIdx === idx && 'bg-tollerud-noir-700'\n )}\n >\n {opt.label}\n </button>\n ))}\n </div>\n )}\n </div>\n {error && (\n <p className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>\n )}\n </div>\n )\n }\n)\nSelect.displayName = 'Select'\n\nexport { Select }"]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { useId, useRef, useState, useMemo, useEffect } from 'react';
|
|
4
|
+
import { ChevronDown, Check } from 'lucide-react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var defaultFilter = (option, query) => option.label.toLowerCase().includes(query.toLowerCase());
|
|
8
|
+
function Combobox({
|
|
9
|
+
options,
|
|
10
|
+
value: valueProp,
|
|
11
|
+
defaultValue,
|
|
12
|
+
onChange,
|
|
13
|
+
placeholder = "Search\u2026",
|
|
14
|
+
label,
|
|
15
|
+
error,
|
|
16
|
+
filter = defaultFilter,
|
|
17
|
+
className,
|
|
18
|
+
disabled
|
|
19
|
+
}) {
|
|
20
|
+
const id = useId();
|
|
21
|
+
const rootRef = useRef(null);
|
|
22
|
+
const isControlled = valueProp !== void 0;
|
|
23
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
24
|
+
const value = isControlled ? valueProp : internalValue;
|
|
25
|
+
const [open, setOpen] = useState(false);
|
|
26
|
+
const [query, setQuery] = useState("");
|
|
27
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
28
|
+
const selected = options.find((o) => o.value === value);
|
|
29
|
+
const filtered = useMemo(() => {
|
|
30
|
+
if (!query) return options;
|
|
31
|
+
return options.filter((o) => filter(o, query));
|
|
32
|
+
}, [options, query, filter]);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!open) return;
|
|
35
|
+
function onClickOutside(e) {
|
|
36
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
37
|
+
setOpen(false);
|
|
38
|
+
setQuery("");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function onResize() {
|
|
42
|
+
setOpen(false);
|
|
43
|
+
setQuery("");
|
|
44
|
+
}
|
|
45
|
+
document.addEventListener("mousedown", onClickOutside);
|
|
46
|
+
window.addEventListener("resize", onResize);
|
|
47
|
+
return () => {
|
|
48
|
+
document.removeEventListener("mousedown", onClickOutside);
|
|
49
|
+
window.removeEventListener("resize", onResize);
|
|
50
|
+
};
|
|
51
|
+
}, [open]);
|
|
52
|
+
const commit = (option) => {
|
|
53
|
+
if (option.disabled) return;
|
|
54
|
+
if (!isControlled) setInternalValue(option.value);
|
|
55
|
+
onChange?.(option.value);
|
|
56
|
+
setOpen(false);
|
|
57
|
+
setQuery("");
|
|
58
|
+
};
|
|
59
|
+
const onKeyDown = (e) => {
|
|
60
|
+
if (e.key === "ArrowDown") {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
setOpen(true);
|
|
63
|
+
setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
|
|
64
|
+
} else if (e.key === "ArrowUp") {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
67
|
+
} else if (e.key === "Enter") {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
const opt = filtered[activeIndex];
|
|
70
|
+
if (opt) commit(opt);
|
|
71
|
+
} else if (e.key === "Escape") {
|
|
72
|
+
setOpen(false);
|
|
73
|
+
setQuery("");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative flex flex-col gap-1", className), children: [
|
|
77
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: label }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
"input",
|
|
81
|
+
{
|
|
82
|
+
id,
|
|
83
|
+
role: "combobox",
|
|
84
|
+
"aria-expanded": open,
|
|
85
|
+
"aria-autocomplete": "list",
|
|
86
|
+
"aria-controls": `${id}-listbox`,
|
|
87
|
+
disabled,
|
|
88
|
+
value: open ? query : selected?.label ?? "",
|
|
89
|
+
placeholder: selected ? selected.label : placeholder,
|
|
90
|
+
onFocus: () => {
|
|
91
|
+
setOpen(true);
|
|
92
|
+
setActiveIndex(0);
|
|
93
|
+
},
|
|
94
|
+
onChange: (e) => {
|
|
95
|
+
setQuery(e.target.value);
|
|
96
|
+
setActiveIndex(0);
|
|
97
|
+
if (!open) setOpen(true);
|
|
98
|
+
},
|
|
99
|
+
onKeyDown,
|
|
100
|
+
className: cn(
|
|
101
|
+
"w-full font-sans text-base px-3 py-2 pr-9 rounded",
|
|
102
|
+
"bg-tollerud-surface-raised border",
|
|
103
|
+
"text-tollerud-text-primary placeholder:text-tollerud-text-muted",
|
|
104
|
+
"transition-[border-color] duration-[150ms]",
|
|
105
|
+
"focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]",
|
|
106
|
+
error ? "border-tollerud-error" : "border-tollerud-border",
|
|
107
|
+
disabled && "opacity-50 pointer-events-none"
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
ChevronDown,
|
|
113
|
+
{
|
|
114
|
+
size: 15,
|
|
115
|
+
className: cn(
|
|
116
|
+
"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-[150ms]",
|
|
117
|
+
open && "rotate-180"
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
] }),
|
|
122
|
+
open && /* @__PURE__ */ jsxs(
|
|
123
|
+
"ul",
|
|
124
|
+
{
|
|
125
|
+
id: `${id}-listbox`,
|
|
126
|
+
role: "listbox",
|
|
127
|
+
className: "absolute top-full z-20 mt-1 max-h-64 w-full overflow-auto rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1 shadow-lg",
|
|
128
|
+
children: [
|
|
129
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-tollerud-text-muted", children: "No results" }),
|
|
130
|
+
filtered.map((option, i) => {
|
|
131
|
+
const isSelected = option.value === value;
|
|
132
|
+
return /* @__PURE__ */ jsxs(
|
|
133
|
+
"li",
|
|
134
|
+
{
|
|
135
|
+
role: "option",
|
|
136
|
+
"aria-selected": isSelected,
|
|
137
|
+
onMouseDown: (e) => {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
commit(option);
|
|
140
|
+
},
|
|
141
|
+
onMouseEnter: () => setActiveIndex(i),
|
|
142
|
+
className: cn(
|
|
143
|
+
"flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer",
|
|
144
|
+
i === activeIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary",
|
|
145
|
+
option.disabled && "opacity-40 pointer-events-none"
|
|
146
|
+
),
|
|
147
|
+
children: [
|
|
148
|
+
option.label,
|
|
149
|
+
isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" })
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
option.value
|
|
153
|
+
);
|
|
154
|
+
})
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-xs text-tollerud-error mt-0.5", children: error })
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
Combobox.displayName = "Combobox";
|
|
162
|
+
|
|
163
|
+
export { Combobox };
|
|
164
|
+
//# sourceMappingURL=chunk-H3ZVGTJM.js.map
|
|
165
|
+
//# sourceMappingURL=chunk-H3ZVGTJM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/Combobox.tsx"],"names":[],"mappings":";;;;;AA0BA,IAAM,aAAA,GAAgB,CAAC,MAAA,EAAwB,KAAA,KAC7C,MAAA,CAAO,KAAA,CAAM,WAAA,EAAY,CAAE,QAAA,CAAS,KAAA,CAAM,WAAA,EAAa,CAAA;AAEzD,SAAS,QAAA,CAAS;AAAA,EAChB,OAAA;AAAA,EACA,KAAA,EAAO,SAAA;AAAA,EACP,YAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA,GAAc,cAAA;AAAA,EACd,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA,GAAS,aAAA;AAAA,EACT,SAAA;AAAA,EACA;AACF,CAAA,EAAkB;AAChB,EAAA,MAAM,KAAK,KAAA,EAAM;AACjB,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,eAAe,SAAA,KAAc,MAAA;AACnC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,YAAY,CAAA;AAC/D,EAAA,MAAM,KAAA,GAAQ,eAAe,SAAA,GAAY,aAAA;AAEzC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,EAAE,CAAA;AACrC,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,CAAC,CAAA;AAEhD,EAAA,MAAM,WAAW,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,KAAK,CAAA;AAEtD,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,CAAC,OAAO,OAAO,OAAA;AACnB,IAAA,OAAO,QAAQ,MAAA,CAAO,CAAC,MAAM,MAAA,CAAO,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,EAC/C,CAAA,EAAG,CAAC,OAAA,EAAS,KAAA,EAAO,MAAM,CAAC,CAAA;AAE3B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,SAAS,eAAe,CAAA,EAAe;AACrC,MAAA,IAAI,OAAA,CAAQ,WAAW,CAAC,OAAA,CAAQ,QAAQ,QAAA,CAAS,CAAA,CAAE,MAAc,CAAA,EAAG;AAClE,QAAA,OAAA,CAAQ,KAAK,CAAA;AACb,QAAA,QAAA,CAAS,EAAE,CAAA;AAAA,MACb;AAAA,IACF;AACA,IAAA,SAAS,QAAA,GAAW;AAClB,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA,QAAA,CAAS,EAAE,CAAA;AAAA,IACb;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,cAAc,CAAA;AACrD,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AAC1C,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,cAAc,CAAA;AACxD,MAAA,MAAA,CAAO,mBAAA,CAAoB,UAAU,QAAQ,CAAA;AAAA,IAC/C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAA2B;AACzC,IAAA,IAAI,OAAO,QAAA,EAAU;AACrB,IAAA,IAAI,CAAC,YAAA,EAAc,gBAAA,CAAiB,MAAA,CAAO,KAAK,CAAA;AAChD,IAAA,QAAA,GAAW,OAAO,KAAK,CAAA;AACvB,IAAA,OAAA,CAAQ,KAAK,CAAA;AACb,IAAA,QAAA,CAAS,EAAE,CAAA;AAAA,EACb,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAA2B;AAC5C,IAAA,IAAI,CAAA,CAAE,QAAQ,WAAA,EAAa;AACzB,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,cAAA,CAAe,CAAC,MAAM,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA,EAAG,QAAA,CAAS,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,IAC5D,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,SAAA,EAAW;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,cAAA,CAAe,CAAC,CAAA,KAAM,IAAA,CAAK,IAAI,CAAA,GAAI,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,IAC1C,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,OAAA,EAAS;AAC5B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAM,GAAA,GAAM,SAAS,WAAW,CAAA;AAChC,MAAA,IAAI,GAAA,SAAY,GAAG,CAAA;AAAA,IACrB,CAAA,MAAA,IAAW,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU;AAC7B,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA,QAAA,CAAS,EAAE,CAAA;AAAA,IACb;AAAA,EACF,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,SAAI,GAAA,EAAK,OAAA,EAAS,WAAW,EAAA,CAAG,8BAAA,EAAgC,SAAS,CAAA,EACvE,QAAA,EAAA;AAAA,IAAA,KAAA,wBACE,OAAA,EAAA,EAAM,OAAA,EAAS,EAAA,EAAI,SAAA,EAAU,gDAC3B,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,oBAEF,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,UAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,EAAA;AAAA,UACA,IAAA,EAAK,UAAA;AAAA,UACL,eAAA,EAAe,IAAA;AAAA,UACf,mBAAA,EAAkB,MAAA;AAAA,UAClB,eAAA,EAAe,GAAG,EAAE,CAAA,QAAA,CAAA;AAAA,UACpB,QAAA;AAAA,UACA,KAAA,EAAO,IAAA,GAAO,KAAA,GAAQ,QAAA,EAAU,KAAA,IAAS,EAAA;AAAA,UACzC,WAAA,EAAa,QAAA,GAAW,QAAA,CAAS,KAAA,GAAQ,WAAA;AAAA,UACzC,SAAS,MAAM;AACb,YAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,YAAA,cAAA,CAAe,CAAC,CAAA;AAAA,UAClB,CAAA;AAAA,UACA,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,YAAA,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AACvB,YAAA,cAAA,CAAe,CAAC,CAAA;AAChB,YAAA,IAAI,CAAC,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAA;AAAA,UACzB,CAAA;AAAA,UACA,SAAA;AAAA,UACA,SAAA,EAAW,EAAA;AAAA,YACT,mDAAA;AAAA,YACA,mCAAA;AAAA,YACA,iEAAA;AAAA,YACA,4CAAA;AAAA,YACA,kFAAA;AAAA,YACA,QAAQ,uBAAA,GAA0B,wBAAA;AAAA,YAClC,QAAA,IAAY;AAAA;AACd;AAAA,OACF;AAAA,sBACA,GAAA;AAAA,QAAC,WAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAM,EAAA;AAAA,UACN,SAAA,EAAW,EAAA;AAAA,YACT,8HAAA;AAAA,YACA,IAAA,IAAQ;AAAA;AACV;AAAA;AACF,KAAA,EACF,CAAA;AAAA,IAEC,IAAA,oBACC,IAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,GAAG,EAAE,CAAA,QAAA,CAAA;AAAA,QACT,IAAA,EAAK,SAAA;AAAA,QACL,SAAA,EAAU,+IAAA;AAAA,QAET,QAAA,EAAA;AAAA,UAAA,QAAA,CAAS,WAAW,CAAA,oBACnB,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,8CAA6C,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,UAEtE,QAAA,CAAS,GAAA,CAAI,CAAC,MAAA,EAAQ,CAAA,KAAM;AAC3B,YAAA,MAAM,UAAA,GAAa,OAAO,KAAA,KAAU,KAAA;AACpC,YAAA,uBACE,IAAA;AAAA,cAAC,IAAA;AAAA,cAAA;AAAA,gBAEC,IAAA,EAAK,QAAA;AAAA,gBACL,eAAA,EAAe,UAAA;AAAA,gBACf,WAAA,EAAa,CAAC,CAAA,KAAM;AAClB,kBAAA,CAAA,CAAE,cAAA,EAAe;AACjB,kBAAA,MAAA,CAAO,MAAM,CAAA;AAAA,gBACf,CAAA;AAAA,gBACA,YAAA,EAAc,MAAM,cAAA,CAAe,CAAC,CAAA;AAAA,gBACpC,SAAA,EAAW,EAAA;AAAA,kBACT,0EAAA;AAAA,kBACA,CAAA,KAAM,cAAc,sDAAA,GAAyD,8BAAA;AAAA,kBAC7E,OAAO,QAAA,IAAY;AAAA,iBACrB;AAAA,gBAEC,QAAA,EAAA;AAAA,kBAAA,MAAA,CAAO,KAAA;AAAA,kBACP,8BAAc,GAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAM,EAAA,EAAI,WAAU,sBAAA,EAAuB;AAAA;AAAA,eAAA;AAAA,cAf5D,MAAA,CAAO;AAAA,aAgBd;AAAA,UAEJ,CAAC;AAAA;AAAA;AAAA,KACH;AAAA,IAGD,KAAA,oBAAS,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,sCAAsC,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EACrE,CAAA;AAEJ;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"chunk-H3ZVGTJM.js","sourcesContent":["'use client'\n\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { Check, ChevronDown } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\nexport interface ComboboxOption {\n value: string\n label: string\n disabled?: boolean\n}\n\nexport interface ComboboxProps {\n options: ComboboxOption[]\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n placeholder?: string\n label?: string\n error?: string\n /** Filter predicate, defaults to a case-insensitive substring match on the label */\n filter?: (option: ComboboxOption, query: string) => boolean\n className?: string\n disabled?: boolean\n}\n\nconst defaultFilter = (option: ComboboxOption, query: string) =>\n option.label.toLowerCase().includes(query.toLowerCase())\n\nfunction Combobox({\n options,\n value: valueProp,\n defaultValue,\n onChange,\n placeholder = 'Search…',\n label,\n error,\n filter = defaultFilter,\n className,\n disabled,\n}: ComboboxProps) {\n const id = useId()\n const rootRef = useRef<HTMLDivElement>(null)\n const isControlled = valueProp !== undefined\n const [internalValue, setInternalValue] = useState(defaultValue)\n const value = isControlled ? valueProp : internalValue\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [activeIndex, setActiveIndex] = useState(0)\n\n const selected = options.find((o) => o.value === value)\n\n const filtered = useMemo(() => {\n if (!query) return options\n return options.filter((o) => filter(o, query))\n }, [options, query, filter])\n\n useEffect(() => {\n if (!open) return\n function onClickOutside(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) {\n setOpen(false)\n setQuery('')\n }\n }\n function onResize() {\n setOpen(false)\n setQuery('')\n }\n document.addEventListener('mousedown', onClickOutside)\n window.addEventListener('resize', onResize)\n return () => {\n document.removeEventListener('mousedown', onClickOutside)\n window.removeEventListener('resize', onResize)\n }\n }, [open])\n\n const commit = (option: ComboboxOption) => {\n if (option.disabled) return\n if (!isControlled) setInternalValue(option.value)\n onChange?.(option.value)\n setOpen(false)\n setQuery('')\n }\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n setActiveIndex((i) => Math.min(i + 1, filtered.length - 1))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setActiveIndex((i) => Math.max(i - 1, 0))\n } else if (e.key === 'Enter') {\n e.preventDefault()\n const opt = filtered[activeIndex]\n if (opt) commit(opt)\n } else if (e.key === 'Escape') {\n setOpen(false)\n setQuery('')\n }\n }\n\n return (\n <div ref={rootRef} className={cn('relative flex flex-col gap-1', className)}>\n {label && (\n <label htmlFor={id} className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n </label>\n )}\n <div className=\"relative\">\n <input\n id={id}\n role=\"combobox\"\n aria-expanded={open}\n aria-autocomplete=\"list\"\n aria-controls={`${id}-listbox`}\n disabled={disabled}\n value={open ? query : selected?.label ?? ''}\n placeholder={selected ? selected.label : placeholder}\n onFocus={() => {\n setOpen(true)\n setActiveIndex(0)\n }}\n onChange={(e) => {\n setQuery(e.target.value)\n setActiveIndex(0)\n if (!open) setOpen(true)\n }}\n onKeyDown={onKeyDown}\n className={cn(\n 'w-full font-sans text-base px-3 py-2 pr-9 rounded',\n 'bg-tollerud-surface-raised border',\n 'text-tollerud-text-primary placeholder:text-tollerud-text-muted',\n 'transition-[border-color] duration-[150ms]',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_#E8D500]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n disabled && 'opacity-50 pointer-events-none'\n )}\n />\n <ChevronDown\n size={15}\n className={cn(\n 'pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-[150ms]',\n open && 'rotate-180'\n )}\n />\n </div>\n\n {open && (\n <ul\n id={`${id}-listbox`}\n role=\"listbox\"\n className=\"absolute top-full z-20 mt-1 max-h-64 w-full overflow-auto rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1 shadow-lg\"\n >\n {filtered.length === 0 && (\n <li className=\"px-3 py-2 text-sm text-tollerud-text-muted\">No results</li>\n )}\n {filtered.map((option, i) => {\n const isSelected = option.value === value\n return (\n <li\n key={option.value}\n role=\"option\"\n aria-selected={isSelected}\n onMouseDown={(e) => {\n e.preventDefault()\n commit(option)\n }}\n onMouseEnter={() => setActiveIndex(i)}\n className={cn(\n 'flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer',\n i === activeIndex ? 'bg-tollerud-surface-hover text-tollerud-text-primary' : 'text-tollerud-text-secondary',\n option.disabled && 'opacity-40 pointer-events-none'\n )}\n >\n {option.label}\n {isSelected && <Check size={14} className=\"text-tollerud-yellow\" />}\n </li>\n )\n })}\n </ul>\n )}\n\n {error && <p className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>}\n </div>\n )\n}\nCombobox.displayName = 'Combobox'\n\nexport { Combobox }\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { cn } from './chunk-WSQNPRGN.js';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Kbd = forwardRef(
|
|
7
|
+
({ className, keys, size = "md", ...props }, ref) => {
|
|
8
|
+
const keyArray = typeof keys === "string" ? [keys] : keys;
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"span",
|
|
11
|
+
{
|
|
12
|
+
ref,
|
|
13
|
+
className: cn(
|
|
14
|
+
"tollerud-kbd",
|
|
15
|
+
size === "sm" && "tollerud-kbd--sm",
|
|
16
|
+
className
|
|
17
|
+
),
|
|
18
|
+
...props,
|
|
19
|
+
children: keyArray.map((key, i) => /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: key }, i))
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
Kbd.displayName = "Kbd";
|
|
25
|
+
|
|
26
|
+
export { Kbd };
|
|
27
|
+
//# sourceMappingURL=chunk-HWAWUEHC.js.map
|
|
28
|
+
//# sourceMappingURL=chunk-HWAWUEHC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../components/Kbd.tsx"],"names":[],"mappings":";;;;AAwBA,IAAM,GAAA,GAAM,UAAA;AAAA,EACV,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,OAAO,IAAA,EAAM,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACnD,IAAA,MAAM,WAAW,OAAO,IAAA,KAAS,QAAA,GAAW,CAAC,IAAI,CAAA,GAAI,IAAA;AAErD,IAAA,uBACE,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,cAAA;AAAA,UACA,SAAS,IAAA,IAAQ,kBAAA;AAAA,UACjB;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,EAAK,CAAA,qBAClB,GAAA,CAAC,MAAA,EAAA,EAAa,SAAA,EAAU,mBAAA,EACrB,QAAA,EAAA,GAAA,EAAA,EADQ,CAEX,CACD;AAAA;AAAA,KACH;AAAA,EAEJ;AACF;AACA,GAAA,CAAI,WAAA,GAAc,KAAA","file":"chunk-HWAWUEHC.js","sourcesContent":["\"use client\"\n\nimport { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface KbdProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * The keys to display. Separate by + for chords.\n * @example \"⌘K\", \"⌘⇧S\", \"⌘K\", \"Esc\"\n */\n keys: string | string[]\n /** Small variant for inline use */\n size?: 'sm' | 'md'\n}\n\n/**\n * Keyboard shortcut chip — inspired by Raycast shortcut badges.\n *\n * ```tsx\n * <Kbd keys=\"⌘K\" />\n * <Kbd keys={[\"⌘\", \"⇧\", \"S\"]} />\n * <Kbd keys=\"Esc\" size=\"sm\" />\n * ```\n */\nconst Kbd = forwardRef<HTMLSpanElement, KbdProps>(\n ({ className, keys, size = 'md', ...props }, ref) => {\n const keyArray = typeof keys === 'string' ? [keys] : keys\n\n return (\n <span\n ref={ref}\n className={cn(\n 'tollerud-kbd',\n size === 'sm' && 'tollerud-kbd--sm',\n className\n )}\n {...props}\n >\n {keyArray.map((key, i) => (\n <span key={i} className=\"tollerud-kbd__key\">\n {key}\n </span>\n ))}\n </span>\n )\n }\n)\nKbd.displayName = 'Kbd'\n\nexport { Kbd }"]}
|