@olympusoss/canvas 2.8.6 → 2.9.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/package.json +1 -1
- package/src/components/charts/activity-heatmap.tsx +9 -2
- package/src/components/charts/chart-container.tsx +63 -4
- package/src/components/molecules/code-block.tsx +44 -7
- package/src/components/molecules/launcher-card.tsx +152 -0
- package/src/components/molecules/terminal.tsx +73 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../lib/utils";
|
|
4
4
|
|
|
5
|
+
const DEFAULT_WEEKDAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] as const;
|
|
6
|
+
|
|
5
7
|
export interface ActivityHeatmapProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
8
|
/**
|
|
7
9
|
* Cell values in row-major order. Each entry is a number in `[0, 1]`
|
|
@@ -81,11 +83,16 @@ export const ActivityHeatmap = React.forwardRef<HTMLDivElement, ActivityHeatmapP
|
|
|
81
83
|
const fromLabel = legendObj?.fromLabel ?? "Fewer";
|
|
82
84
|
const toLabel = legendObj?.toLabel ?? "More";
|
|
83
85
|
const showLegend = legend !== false;
|
|
86
|
+
// 7-row grids default to weekday labels (Mon–Sun) — covers the common
|
|
87
|
+
// GitHub-style yearly contribution pattern without each consumer
|
|
88
|
+
// re-declaring the array.
|
|
89
|
+
const resolvedRowLabels =
|
|
90
|
+
rowLabels ?? (data.length === 7 ? DEFAULT_WEEKDAY_LABELS.slice() : undefined);
|
|
84
91
|
|
|
85
92
|
return (
|
|
86
93
|
<div ref={ref} className={cn("w-full", className)} {...props}>
|
|
87
94
|
<div className="flex gap-2">
|
|
88
|
-
{
|
|
95
|
+
{resolvedRowLabels && resolvedRowLabels.length > 0 && (
|
|
89
96
|
<div
|
|
90
97
|
className="grid text-[10px] tabular-nums text-muted-foreground"
|
|
91
98
|
style={{
|
|
@@ -96,7 +103,7 @@ export const ActivityHeatmap = React.forwardRef<HTMLDivElement, ActivityHeatmapP
|
|
|
96
103
|
>
|
|
97
104
|
{Array.from({ length: data.length }, (_, i) => (
|
|
98
105
|
<span key={`row-label-${i}`} className="flex items-center leading-none">
|
|
99
|
-
{
|
|
106
|
+
{resolvedRowLabels[i] ?? ""}
|
|
100
107
|
</span>
|
|
101
108
|
))}
|
|
102
109
|
</div>
|
|
@@ -29,6 +29,36 @@ const PALETTE_TARGETS = new Set<unknown>([
|
|
|
29
29
|
|
|
30
30
|
const PALETTE_SIZE = 5;
|
|
31
31
|
|
|
32
|
+
function nextColour(counter: { i: number }) {
|
|
33
|
+
const idx = counter.i++ % PALETTE_SIZE;
|
|
34
|
+
return `hsl(var(--chart-${idx + 1}))`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Walk a Pie's `<Cell>` children and inject per-cell palette fills when none
|
|
39
|
+
* is set, so each slice gets a distinct hue without requiring the consumer to
|
|
40
|
+
* write `<Cell fill={...} />` for every entry.
|
|
41
|
+
*/
|
|
42
|
+
function paintPieCells(children: React.ReactNode, counter: { i: number }): React.ReactNode {
|
|
43
|
+
return React.Children.map(children, (child) => {
|
|
44
|
+
if (!React.isValidElement(child)) return child;
|
|
45
|
+
const cellProps = child.props as { fill?: unknown };
|
|
46
|
+
if (child.type === RechartsPrimitive.Cell && cellProps.fill === undefined) {
|
|
47
|
+
return React.cloneElement(child, { fill: nextColour(counter) } as Partial<typeof cellProps>);
|
|
48
|
+
}
|
|
49
|
+
return child;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Inject per-row `fill` on Funnel data when the consumer didn't supply one.
|
|
55
|
+
* Recharts Funnel reads each datum's `fill` to colour the stage, so we map
|
|
56
|
+
* the array and assign palette colours in order.
|
|
57
|
+
*/
|
|
58
|
+
function paintFunnelData<T extends { fill?: unknown }>(data: T[], counter: { i: number }): T[] {
|
|
59
|
+
return data.map((row) => (row.fill === undefined ? { ...row, fill: nextColour(counter) } : row));
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
/**
|
|
33
63
|
* Recursively walk the children, cloning any data primitive that's missing
|
|
34
64
|
* `fill`/`stroke` and injecting an `hsl(var(--chart-N))` default. Counter is
|
|
@@ -37,12 +67,41 @@ const PALETTE_SIZE = 5;
|
|
|
37
67
|
function applyPalette(children: React.ReactNode, counter: { i: number }): React.ReactNode {
|
|
38
68
|
return React.Children.map(children, (child) => {
|
|
39
69
|
if (!React.isValidElement(child)) return child;
|
|
40
|
-
const props = child.props as {
|
|
70
|
+
const props = child.props as {
|
|
71
|
+
fill?: unknown;
|
|
72
|
+
stroke?: unknown;
|
|
73
|
+
children?: React.ReactNode;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
label?: unknown;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Pie: walk Cell children and assign per-slice palette colours so each
|
|
79
|
+
// slice is distinct. Also default `label={true}` so slices render their
|
|
80
|
+
// data-key value at the perimeter without needing a `<LabelList>`.
|
|
81
|
+
if (child.type === RechartsPrimitive.Pie) {
|
|
82
|
+
const next: Record<string, unknown> = {};
|
|
83
|
+
if (props.children !== undefined) {
|
|
84
|
+
next.children = paintPieCells(props.children, counter);
|
|
85
|
+
} else if (props.fill === undefined && props.stroke === undefined) {
|
|
86
|
+
const colour = nextColour(counter);
|
|
87
|
+
next.fill = colour;
|
|
88
|
+
next.stroke = colour;
|
|
89
|
+
}
|
|
90
|
+
if (props.label === undefined) next.label = true;
|
|
91
|
+
return React.cloneElement(child, next as Partial<typeof props>);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Funnel: distribute palette colours across the data array (Recharts
|
|
95
|
+
// reads `fill` from each datum, not from the <Funnel> element).
|
|
96
|
+
if (child.type === RechartsPrimitive.Funnel && Array.isArray(props.data)) {
|
|
97
|
+
return React.cloneElement(child, {
|
|
98
|
+
data: paintFunnelData(props.data as { fill?: unknown }[], counter),
|
|
99
|
+
} as Partial<typeof props>);
|
|
100
|
+
}
|
|
41
101
|
|
|
42
102
|
if (PALETTE_TARGETS.has(child.type)) {
|
|
43
103
|
if (props.fill === undefined && props.stroke === undefined) {
|
|
44
|
-
const
|
|
45
|
-
const colour = `hsl(var(--chart-${idx + 1}))`;
|
|
104
|
+
const colour = nextColour(counter);
|
|
46
105
|
// Pie/Radar/Area also benefit from a matching stroke; the
|
|
47
106
|
// component decides which one applies (Bar reads fill, Line
|
|
48
107
|
// reads stroke, etc.).
|
|
@@ -115,7 +174,7 @@ const ChartContainer = React.forwardRef<
|
|
|
115
174
|
data-chart={chartId}
|
|
116
175
|
ref={ref}
|
|
117
176
|
className={cn(
|
|
118
|
-
"flex aspect-video w-full justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
177
|
+
"flex aspect-video w-full justify-center text-xs [&_.recharts-cartesian-axis-line]:stroke-border [&_.recharts-cartesian-axis-tick-line]:stroke-transparent [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
|
119
178
|
className,
|
|
120
179
|
)}
|
|
121
180
|
{...props}
|
|
@@ -10,11 +10,18 @@ export interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
10
10
|
code: string;
|
|
11
11
|
language?: string;
|
|
12
12
|
showCopy?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* `"light"` (default) renders against `bg-muted`; `"dark"` switches to a
|
|
15
|
+
* near-black terminal palette (`#0a0a0b` background, `#e4e4e7` text) for
|
|
16
|
+
* use on marketing surfaces.
|
|
17
|
+
*/
|
|
18
|
+
theme?: "light" | "dark";
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
|
|
16
|
-
({ code, language, showCopy = true, className, ...props }, ref) => {
|
|
22
|
+
({ code, language, showCopy = true, theme = "light", className, ...props }, ref) => {
|
|
17
23
|
const [copied, setCopied] = React.useState(false);
|
|
24
|
+
const isDark = theme === "dark";
|
|
18
25
|
|
|
19
26
|
const handleCopy = React.useCallback(async () => {
|
|
20
27
|
await navigator.clipboard.writeText(code);
|
|
@@ -23,20 +30,50 @@ const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
|
|
|
23
30
|
}, [code]);
|
|
24
31
|
|
|
25
32
|
return (
|
|
26
|
-
<div
|
|
33
|
+
<div
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn(
|
|
36
|
+
"relative overflow-hidden rounded-md",
|
|
37
|
+
isDark ? "border border-border" : "bg-muted",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
style={isDark ? { background: "#0a0a0b" } : undefined}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
27
43
|
{(language || showCopy) && (
|
|
28
|
-
<div
|
|
29
|
-
{
|
|
30
|
-
|
|
44
|
+
<div
|
|
45
|
+
className={cn(
|
|
46
|
+
"flex items-center justify-between px-4 py-2",
|
|
47
|
+
isDark ? undefined : "border-b",
|
|
48
|
+
)}
|
|
49
|
+
style={isDark ? { borderBottom: "1px solid #222" } : undefined}
|
|
50
|
+
>
|
|
51
|
+
{language ? (
|
|
52
|
+
<span
|
|
53
|
+
className={cn("text-xs font-medium", !isDark && "text-muted-foreground")}
|
|
54
|
+
style={isDark ? { color: "#6b7280" } : undefined}
|
|
55
|
+
>
|
|
56
|
+
{language}
|
|
57
|
+
</span>
|
|
58
|
+
) : (
|
|
59
|
+
<span />
|
|
31
60
|
)}
|
|
32
61
|
{showCopy && (
|
|
33
|
-
<Button
|
|
62
|
+
<Button
|
|
63
|
+
variant="ghost"
|
|
64
|
+
size="icon"
|
|
65
|
+
className={cn(
|
|
66
|
+
"h-6 w-6",
|
|
67
|
+
isDark && "text-zinc-400 hover:bg-white/5 hover:text-zinc-100",
|
|
68
|
+
)}
|
|
69
|
+
onClick={handleCopy}
|
|
70
|
+
>
|
|
34
71
|
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
|
35
72
|
</Button>
|
|
36
73
|
)}
|
|
37
74
|
</div>
|
|
38
75
|
)}
|
|
39
|
-
<pre className="overflow-x-auto p-4">
|
|
76
|
+
<pre className="overflow-x-auto p-4" style={isDark ? { color: "#e4e4e7" } : undefined}>
|
|
40
77
|
<code className="text-sm">{code}</code>
|
|
41
78
|
</pre>
|
|
42
79
|
</div>
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// molecules: can import tokens/, lib/utils, atoms/.
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils";
|
|
7
|
+
|
|
8
|
+
const TONE_STYLES = {
|
|
9
|
+
default: {
|
|
10
|
+
bg: "hsl(var(--primary) / 0.1)",
|
|
11
|
+
fg: "hsl(var(--primary))",
|
|
12
|
+
ring: "hsl(var(--primary) / 0.2)",
|
|
13
|
+
},
|
|
14
|
+
indigo: {
|
|
15
|
+
bg: "hsl(231 92% 96%)",
|
|
16
|
+
fg: "hsl(231 60% 38%)",
|
|
17
|
+
ring: "hsl(231 80% 60% / 0.25)",
|
|
18
|
+
},
|
|
19
|
+
violet: {
|
|
20
|
+
bg: "hsl(262 90% 96%)",
|
|
21
|
+
fg: "hsl(262 55% 42%)",
|
|
22
|
+
ring: "hsl(262 80% 60% / 0.25)",
|
|
23
|
+
},
|
|
24
|
+
slate: {
|
|
25
|
+
bg: "hsl(230 25% 95%)",
|
|
26
|
+
fg: "hsl(230 30% 30%)",
|
|
27
|
+
ring: "hsl(230 20% 60% / 0.25)",
|
|
28
|
+
},
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export type LauncherCardTone = keyof typeof TONE_STYLES;
|
|
32
|
+
|
|
33
|
+
export interface LauncherCardProps {
|
|
34
|
+
/**
|
|
35
|
+
* Top-left identifier — typically a letter (`"C"`) or a small icon.
|
|
36
|
+
*/
|
|
37
|
+
badge: React.ReactNode;
|
|
38
|
+
/** Card heading. */
|
|
39
|
+
title: string;
|
|
40
|
+
/** One-or-two sentence body copy below the title. */
|
|
41
|
+
description: string;
|
|
42
|
+
/**
|
|
43
|
+
* Colour key for the badge background, foreground, and hover ring.
|
|
44
|
+
* Defaults to `"default"`, which uses the `--primary` token.
|
|
45
|
+
*/
|
|
46
|
+
tone?: LauncherCardTone;
|
|
47
|
+
/**
|
|
48
|
+
* If set, the entire card becomes a link with a hover-lift effect.
|
|
49
|
+
* Pair with `linkComponent` to route through a framework-specific Link
|
|
50
|
+
* component (e.g. Next.js).
|
|
51
|
+
*/
|
|
52
|
+
href?: string;
|
|
53
|
+
/**
|
|
54
|
+
* `target` attribute applied when `href` is set. Defaults to `"_self"`.
|
|
55
|
+
*/
|
|
56
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
57
|
+
/**
|
|
58
|
+
* `rel` attribute applied when `href` is set. Defaults to `"noopener"`
|
|
59
|
+
* when `target === "_blank"`.
|
|
60
|
+
*/
|
|
61
|
+
rel?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Override the rendered link element when `href` is set
|
|
64
|
+
* (e.g. `Link` from `next/link`). Falls back to `<a>`.
|
|
65
|
+
*/
|
|
66
|
+
linkComponent?: React.ElementType;
|
|
67
|
+
/**
|
|
68
|
+
* Footer slot. Typically a small CTA row (e.g. arrow link) or, when the
|
|
69
|
+
* card is non-interactive, an inline status / detail block.
|
|
70
|
+
*/
|
|
71
|
+
children?: React.ReactNode;
|
|
72
|
+
className?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SHARED_CLASSES =
|
|
76
|
+
"group flex flex-col gap-4 rounded-[14px] border border-border bg-card p-6 text-card-foreground";
|
|
77
|
+
|
|
78
|
+
const INTERACTIVE_CLASSES =
|
|
79
|
+
"no-underline transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:shadow-[0_20px_40px_-20px_var(--launcher-tone-ring),0_8px_16px_-8px_rgb(0_0_0/0.08)] hover:[border-color:var(--launcher-tone-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
|
|
80
|
+
|
|
81
|
+
const LauncherCard = React.forwardRef<HTMLElement, LauncherCardProps>(
|
|
82
|
+
(
|
|
83
|
+
{
|
|
84
|
+
badge,
|
|
85
|
+
title,
|
|
86
|
+
description,
|
|
87
|
+
tone = "default",
|
|
88
|
+
href,
|
|
89
|
+
target,
|
|
90
|
+
rel,
|
|
91
|
+
linkComponent,
|
|
92
|
+
children,
|
|
93
|
+
className,
|
|
94
|
+
...props
|
|
95
|
+
},
|
|
96
|
+
ref,
|
|
97
|
+
) => {
|
|
98
|
+
const t = TONE_STYLES[tone];
|
|
99
|
+
const interactive = Boolean(href);
|
|
100
|
+
const toneVars = {
|
|
101
|
+
"--launcher-tone-fg": t.fg,
|
|
102
|
+
"--launcher-tone-ring": t.ring,
|
|
103
|
+
} as React.CSSProperties;
|
|
104
|
+
|
|
105
|
+
const inner = (
|
|
106
|
+
<>
|
|
107
|
+
<div className="flex items-center gap-3.5">
|
|
108
|
+
<div
|
|
109
|
+
className="flex h-11 w-11 items-center justify-center rounded-[10px] text-lg font-semibold tracking-tight"
|
|
110
|
+
style={{ background: t.bg, color: t.fg }}
|
|
111
|
+
>
|
|
112
|
+
{badge}
|
|
113
|
+
</div>
|
|
114
|
+
<div className="text-[17px] font-semibold tracking-tight">{title}</div>
|
|
115
|
+
</div>
|
|
116
|
+
<p className="m-0 flex-1 text-sm leading-relaxed text-muted-foreground">{description}</p>
|
|
117
|
+
{children != null && <div className="mt-1">{children}</div>}
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (interactive) {
|
|
122
|
+
const LinkEl = linkComponent || "a";
|
|
123
|
+
const resolvedRel = rel ?? (target === "_blank" ? "noopener" : undefined);
|
|
124
|
+
return (
|
|
125
|
+
<LinkEl
|
|
126
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
127
|
+
href={href}
|
|
128
|
+
target={target}
|
|
129
|
+
rel={resolvedRel}
|
|
130
|
+
className={cn(SHARED_CLASSES, INTERACTIVE_CLASSES, className)}
|
|
131
|
+
style={toneVars}
|
|
132
|
+
{...props}
|
|
133
|
+
>
|
|
134
|
+
{inner}
|
|
135
|
+
</LinkEl>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
ref={ref as React.Ref<HTMLDivElement>}
|
|
142
|
+
className={cn(SHARED_CLASSES, className)}
|
|
143
|
+
{...props}
|
|
144
|
+
>
|
|
145
|
+
{inner}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
LauncherCard.displayName = "LauncherCard";
|
|
151
|
+
|
|
152
|
+
export { LauncherCard };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// molecules: can import tokens/, lib/utils, atoms/.
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
|
|
6
|
+
export interface TerminalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
7
|
+
/**
|
|
8
|
+
* Optional label rendered in the macOS-style chrome strip, next to the
|
|
9
|
+
* traffic-light dots. Typical use: a working directory + command
|
|
10
|
+
* (e.g. `~/Olympus · octl`).
|
|
11
|
+
*/
|
|
12
|
+
title?: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Terminal body. Renders inside a `<pre>` with mono font and 1.7 line
|
|
15
|
+
* height. Free-form — drop plain text, ANSI-coloured `<span>`s, or any
|
|
16
|
+
* inline highlights you want.
|
|
17
|
+
*/
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const Terminal = React.forwardRef<HTMLDivElement, TerminalProps>(
|
|
23
|
+
({ title, children, className, ...props }, ref) => (
|
|
24
|
+
<div
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn(
|
|
27
|
+
"overflow-hidden rounded-xl border border-border shadow-[0_30px_60px_-20px_rgb(0_0_0/0.35)]",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
style={{ background: "#0a0a0b" }}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
className="flex items-center gap-1.5 px-3"
|
|
35
|
+
style={{
|
|
36
|
+
height: 32,
|
|
37
|
+
background: "#141417",
|
|
38
|
+
borderBottom: "1px solid #222",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<span
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
44
|
+
style={{ background: "#ff5f57" }}
|
|
45
|
+
/>
|
|
46
|
+
<span
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
49
|
+
style={{ background: "#febc2e" }}
|
|
50
|
+
/>
|
|
51
|
+
<span
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
className="inline-block h-2.5 w-2.5 rounded-full"
|
|
54
|
+
style={{ background: "#28c840" }}
|
|
55
|
+
/>
|
|
56
|
+
{title != null && (
|
|
57
|
+
<span className="ml-2.5 font-mono text-[11px]" style={{ color: "#6b7280" }}>
|
|
58
|
+
{title}
|
|
59
|
+
</span>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
<pre
|
|
63
|
+
className="m-0 overflow-x-auto p-5 font-mono text-[12.5px] leading-[1.7]"
|
|
64
|
+
style={{ color: "#e4e4e7" }}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</pre>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
Terminal.displayName = "Terminal";
|
|
72
|
+
|
|
73
|
+
export { Terminal };
|
package/src/index.ts
CHANGED
|
@@ -189,6 +189,11 @@ export {
|
|
|
189
189
|
InputOTPSeparator,
|
|
190
190
|
InputOTPSlot,
|
|
191
191
|
} from "./components/molecules/input-otp";
|
|
192
|
+
export {
|
|
193
|
+
LauncherCard,
|
|
194
|
+
type LauncherCardProps,
|
|
195
|
+
type LauncherCardTone,
|
|
196
|
+
} from "./components/molecules/launcher-card";
|
|
192
197
|
export {
|
|
193
198
|
LoadingState,
|
|
194
199
|
type LoadingStateProps,
|
|
@@ -260,6 +265,7 @@ export {
|
|
|
260
265
|
TableHeader,
|
|
261
266
|
TableRow,
|
|
262
267
|
} from "./components/molecules/table";
|
|
268
|
+
export { Terminal, type TerminalProps } from "./components/molecules/terminal";
|
|
263
269
|
export { ToggleGroup, ToggleGroupItem } from "./components/molecules/toggle-group";
|
|
264
270
|
export {
|
|
265
271
|
Tooltip,
|