jaml-ui 0.21.2 → 0.21.4
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/DESIGN.md +36 -6
- package/dist/components/JamlAnalyzerFullscreen.d.ts +1 -1
- package/dist/components/JamlAnalyzerFullscreen.js +5 -81
- package/dist/components/JamlCurator.js +1 -1
- package/dist/components/JamlSpeedometer.d.ts +7 -2
- package/dist/components/JamlSpeedometer.js +8 -15
- package/dist/components/jamlMap/JamlMapEditor.js +42 -38
- package/dist/components/jamlMap/JokerPicker.js +2 -2
- package/dist/components/jamlMap/MysterySlot.js +4 -4
- package/dist/hooks/useSearch.d.ts +2 -1
- package/dist/hooks/useSearch.js +111 -8
- package/dist/lib/SpriteMapper.d.ts +10 -0
- package/dist/lib/SpriteMapper.js +48 -0
- package/dist/lib/cardParser.d.ts +8 -0
- package/dist/lib/cardParser.js +65 -0
- package/dist/lib/classes/BuyMetaData.d.ts +11 -0
- package/dist/lib/classes/BuyMetaData.js +1 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +15 -0
- package/dist/lib/const.d.ts +61 -0
- package/dist/lib/const.js +521 -0
- package/dist/lib/data/constants.d.ts +11 -0
- package/dist/lib/data/constants.js +17 -0
- package/dist/lib/hooks/useDragScroll.d.ts +4 -0
- package/dist/lib/hooks/useDragScroll.js +48 -0
- package/dist/lib/hooks/useJamlFilter.d.ts +48 -0
- package/dist/lib/hooks/useJamlFilter.js +219 -0
- package/dist/lib/hooks/useSeedAnalyzer.d.ts +6 -0
- package/dist/lib/hooks/useSeedAnalyzer.js +48 -0
- package/dist/lib/jaml/jamlCompletion.d.ts +12 -0
- package/dist/lib/jaml/jamlCompletion.js +13 -0
- package/dist/lib/jaml/jamlData.d.ts +3 -0
- package/dist/lib/jaml/jamlData.js +8 -0
- package/dist/lib/jaml/jamlObjectives.d.ts +13 -0
- package/dist/lib/jaml/jamlObjectives.js +97 -0
- package/dist/lib/jaml/jamlParser.d.ts +14 -0
- package/dist/lib/jaml/jamlParser.js +47 -0
- package/dist/lib/jaml/jamlPresets.d.ts +8 -0
- package/dist/lib/jaml/jamlPresets.js +61 -0
- package/dist/lib/jaml/jamlSchema.d.ts +54 -0
- package/dist/lib/jaml/jamlSchema.js +91 -0
- package/dist/lib/parseDailyRitual.d.ts +45 -0
- package/dist/lib/parseDailyRitual.js +69 -0
- package/dist/lib/tts/getRevealPos.d.ts +5 -0
- package/dist/lib/tts/getRevealPos.js +16 -0
- package/dist/lib/tts/splitTtsDisplay.d.ts +19 -0
- package/dist/lib/tts/splitTtsDisplay.js +35 -0
- package/dist/lib/types.d.ts +121 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/ui/JimboIconButton.d.ts +10 -0
- package/dist/ui/JimboIconButton.js +28 -0
- package/dist/ui/JimboInputModal.d.ts +13 -0
- package/dist/ui/JimboInputModal.js +60 -0
- package/dist/ui/JimboSelect.d.ts +18 -0
- package/dist/ui/JimboSelect.js +43 -0
- package/dist/ui/PanelSplitter.d.ts +7 -0
- package/dist/ui/PanelSplitter.js +76 -0
- package/dist/ui/ide/AgnosticSeedCard.d.ts +19 -0
- package/dist/ui/ide/AgnosticSeedCard.js +48 -0
- package/dist/ui/ide/DeckSprite.d.ts +1 -0
- package/dist/ui/ide/DeckSprite.js +2 -0
- package/dist/ui/ide/JamlBuilder.d.ts +1 -0
- package/dist/ui/ide/JamlBuilder.js +112 -0
- package/dist/ui/ide/JamlEditor.d.ts +7 -0
- package/dist/ui/ide/JamlEditor.js +496 -0
- package/dist/ui/ide/JamlEditorMonaco.d.ts +8 -0
- package/dist/ui/ide/JamlEditorMonaco.js +78 -0
- package/dist/ui/ide/WasmStatus.d.ts +1 -0
- package/dist/ui/ide/WasmStatus.js +42 -0
- package/dist/ui/jimbo.css +336 -31
- package/dist/ui/jimboApp.d.ts +12 -0
- package/dist/ui/jimboApp.js +15 -0
- package/dist/ui/jimboInfoCard.d.ts +31 -0
- package/dist/ui/jimboInfoCard.js +26 -0
- package/dist/ui/jimboInset.d.ts +9 -0
- package/dist/ui/jimboInset.js +9 -0
- package/dist/ui/jimboSectionHeader.d.ts +11 -0
- package/dist/ui/jimboSectionHeader.js +9 -0
- package/dist/ui/jimboStatGrid.d.ts +13 -0
- package/dist/ui/jimboStatGrid.js +9 -0
- package/dist/ui/jimboWordmark.d.ts +10 -0
- package/dist/ui/jimboWordmark.js +9 -0
- package/dist/ui/mascot/JammySpeechBox.d.ts +9 -0
- package/dist/ui/mascot/JammySpeechBox.js +30 -0
- package/dist/ui/mascot/SeedMascot.d.ts +37 -0
- package/dist/ui/mascot/SeedMascot.js +17 -0
- package/dist/ui/mascot/index.d.ts +3 -0
- package/dist/ui/mascot/index.js +3 -0
- package/dist/ui/mascot/menuConfig.d.ts +102 -0
- package/dist/ui/mascot/menuConfig.js +12 -0
- package/dist/ui/panel.d.ts +1 -1
- package/dist/ui/panel.js +3 -21
- package/dist/ui/radial/RadialBadge.d.ts +17 -0
- package/dist/ui/radial/RadialBadge.js +43 -0
- package/dist/ui/radial/RadialBreadcrumb.d.ts +12 -0
- package/dist/ui/radial/RadialBreadcrumb.js +18 -0
- package/dist/ui/radial/RadialButton.d.ts +61 -0
- package/dist/ui/radial/RadialButton.js +102 -0
- package/dist/ui/radial/RadialMenu.d.ts +38 -0
- package/dist/ui/radial/RadialMenu.js +168 -0
- package/dist/ui/radial/RadialPill.d.ts +18 -0
- package/dist/ui/radial/RadialPill.js +15 -0
- package/dist/ui/radial/index.d.ts +16 -0
- package/dist/ui/radial/index.js +18 -0
- package/dist/ui/radial/radialMenuStore.d.ts +31 -0
- package/dist/ui/radial/radialMenuStore.js +122 -0
- package/dist/ui/radial/radialMenuViewport.d.ts +6 -0
- package/dist/ui/radial/radialMenuViewport.js +59 -0
- package/dist/ui/radial/useRadialMenu.d.ts +35 -0
- package/dist/ui/radial/useRadialMenu.js +107 -0
- package/dist/ui/showcase.d.ts +14 -6
- package/dist/ui/showcase.js +13 -21
- package/dist/ui/tokens.d.ts +5 -19
- package/dist/ui/tokens.js +5 -21
- package/dist/ui.d.ts +14 -0
- package/dist/ui.js +15 -0
- package/package.json +145 -146
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { MenuItem } from "./menuConfig";
|
|
3
|
+
export interface SeedMascotProps {
|
|
4
|
+
/** Hit target size for the mascot image (px). Default 160. */
|
|
5
|
+
size?: number;
|
|
6
|
+
/** Vertical translation for keyboard dodge (px). */
|
|
7
|
+
mascotTranslateY?: number;
|
|
8
|
+
/** Extra translateY (px) for welcome entrance sliding animation. */
|
|
9
|
+
welcomeEntranceLift?: number;
|
|
10
|
+
/** CSS class for mascot animations (boing, dance, talking, etc.) */
|
|
11
|
+
mascotMotionClass?: string;
|
|
12
|
+
/** Callback when the mascot itself is tapped. */
|
|
13
|
+
onTap?: () => void;
|
|
14
|
+
/** Whether to show the radial menu ring. */
|
|
15
|
+
showRadialMenu?: boolean;
|
|
16
|
+
/** Whether the radial menu is currently animating out. */
|
|
17
|
+
showClosingRadialMenu?: boolean;
|
|
18
|
+
/** Menu items to display. */
|
|
19
|
+
radialItems?: MenuItem[];
|
|
20
|
+
/** Key for the current menu stack level. */
|
|
21
|
+
currentMenu?: string;
|
|
22
|
+
/** Callback when a menu item is clicked. */
|
|
23
|
+
onItemClick?: (item: MenuItem) => void;
|
|
24
|
+
/** Callback for the Back button. */
|
|
25
|
+
onBack?: () => void;
|
|
26
|
+
/** Show pagination controls. */
|
|
27
|
+
showPageControls?: boolean;
|
|
28
|
+
/** Go to previous orbital menu page. */
|
|
29
|
+
onPagePrev?: () => void;
|
|
30
|
+
/** Go to next orbital menu page. */
|
|
31
|
+
onPageNext?: () => void;
|
|
32
|
+
/** Breadcrumb trail label. */
|
|
33
|
+
breadcrumb?: React.ReactNode;
|
|
34
|
+
/** Base radius for orbit (will scale up if there are many items). */
|
|
35
|
+
baseOrbitRadius?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const SeedMascot: React.NamedExoticComponent<SeedMascotProps>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { RadialMenu } from "../radial";
|
|
4
|
+
export const SeedMascot = React.memo(function SeedMascot({ size = 160, mascotTranslateY = 0, welcomeEntranceLift = 0, mascotMotionClass = "jammy-jimbo-idle", onTap, showRadialMenu = false, showClosingRadialMenu = false, radialItems = [], currentMenu = "main", onItemClick, onBack, showPageControls = false, onPagePrev, onPageNext, breadcrumb, baseOrbitRadius = 66, }) {
|
|
5
|
+
// Orbit geometry — tight ring around Jammy; small per-item nudge only when many slots.
|
|
6
|
+
const orbitalCount = radialItems.filter((it) => !("_south" in it && it._south)).length;
|
|
7
|
+
const mascotHalf = size / 2;
|
|
8
|
+
const minOrbitR = mascotHalf + 42;
|
|
9
|
+
const orbitRadiusX = Math.max(84, baseOrbitRadius + orbitalCount * 3, minOrbitR);
|
|
10
|
+
const orbitRadiusY = orbitRadiusX;
|
|
11
|
+
return (_jsxs("div", { className: "relative flex w-full flex-col items-center justify-center", children: [radialItems.length > 0 && (showRadialMenu || showClosingRadialMenu) ? (_jsx(RadialMenu, { items: radialItems, showClosing: showClosingRadialMenu, mascotSizePx: size, orbitRadiusX: orbitRadiusX, orbitRadiusY: orbitRadiusY, mascotTranslateY: mascotTranslateY, currentMenu: currentMenu, onItemClick: onItemClick || (() => { }), onBack: onBack || (() => { }), showPageControls: showPageControls, onPagePrev: onPagePrev, onPageNext: onPageNext, breadcrumb: breadcrumb })) : null, _jsx("div", { className: "relative z-20 transition-transform duration-500 ease-[cubic-bezier(0.22,0.92,0.2,1.08)]", style: {
|
|
12
|
+
transform: `translateY(${mascotTranslateY + welcomeEntranceLift}px)`,
|
|
13
|
+
}, children: _jsx("button", { type: "button", onClick: onTap, className: "relative block cursor-pointer rounded-full filter-[drop-shadow(0_4_12_rgba(0,0,0,0.5))] transition-transform duration-300 ease-[cubic-bezier(0.22,0.9,0.3,1.12)] will-change-transform [-webkit-backface-visibility:hidden] backface-hidden hover:scale-105 focus-visible:ring-4 focus-visible:ring-orange-500 active:scale-95", style: {
|
|
14
|
+
width: size,
|
|
15
|
+
height: size,
|
|
16
|
+
}, "aria-label": "Tap Jammy to open chat or the menu", children: _jsx("span", { className: `pointer-events-none absolute inset-0 block ${mascotMotionClass}`, style: { transformOrigin: "50% 80%" }, children: _jsx("img", { src: "/jaml-logo.png", alt: "Jammy", className: "absolute inset-0 block h-full w-full object-contain" }) }) }) })] }));
|
|
17
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export declare const MenuColors: {
|
|
2
|
+
SUBMENU: "red";
|
|
3
|
+
CHAT: "blue";
|
|
4
|
+
TOGGLE: "orange";
|
|
5
|
+
ACTION: "red";
|
|
6
|
+
START: "green";
|
|
7
|
+
UPLOAD: "orange";
|
|
8
|
+
};
|
|
9
|
+
export interface IndicatorLightBadge {
|
|
10
|
+
label: string;
|
|
11
|
+
state: "loading" | "success" | "error" | "info" | "warning";
|
|
12
|
+
detail?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SubmenuItem {
|
|
15
|
+
label: string;
|
|
16
|
+
color: typeof MenuColors.SUBMENU;
|
|
17
|
+
badge?: IndicatorLightBadge;
|
|
18
|
+
tooltip?: string;
|
|
19
|
+
active?: never;
|
|
20
|
+
action?: never;
|
|
21
|
+
_dim?: boolean;
|
|
22
|
+
_south?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ChatItem {
|
|
25
|
+
label: string;
|
|
26
|
+
action: string;
|
|
27
|
+
color: typeof MenuColors.CHAT;
|
|
28
|
+
badge?: never;
|
|
29
|
+
tooltip?: string;
|
|
30
|
+
active?: never;
|
|
31
|
+
_dim?: boolean;
|
|
32
|
+
_south?: boolean;
|
|
33
|
+
/** Structured payload — passed through onMenuAction so handlers don't parse action strings. */
|
|
34
|
+
data?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface ToggleItem {
|
|
37
|
+
label: string;
|
|
38
|
+
action: string;
|
|
39
|
+
color: typeof MenuColors.TOGGLE;
|
|
40
|
+
active?: boolean;
|
|
41
|
+
tooltip?: string;
|
|
42
|
+
badge?: never;
|
|
43
|
+
_dim?: boolean;
|
|
44
|
+
_south?: boolean;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface ActionItem {
|
|
48
|
+
label: string;
|
|
49
|
+
action: string;
|
|
50
|
+
color: typeof MenuColors.ACTION;
|
|
51
|
+
badge?: never;
|
|
52
|
+
tooltip?: string;
|
|
53
|
+
active?: never;
|
|
54
|
+
_dim?: boolean;
|
|
55
|
+
_south?: boolean;
|
|
56
|
+
/** Structured payload — passed through onMenuAction so handlers don't parse action strings. */
|
|
57
|
+
data?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export interface StartItem {
|
|
60
|
+
label: string;
|
|
61
|
+
action: string;
|
|
62
|
+
color: typeof MenuColors.START;
|
|
63
|
+
badge?: never;
|
|
64
|
+
tooltip?: "start";
|
|
65
|
+
active?: never;
|
|
66
|
+
_dim?: never;
|
|
67
|
+
_south?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface SeedItem {
|
|
70
|
+
label: string;
|
|
71
|
+
action: string;
|
|
72
|
+
color: "seed-top" | "seed-low" | "seed-zero";
|
|
73
|
+
tooltip?: string;
|
|
74
|
+
badge?: never;
|
|
75
|
+
active?: never;
|
|
76
|
+
_south?: boolean;
|
|
77
|
+
count?: number;
|
|
78
|
+
icon?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface CountIndicatorButtonItem {
|
|
81
|
+
label: string;
|
|
82
|
+
action?: string;
|
|
83
|
+
color: typeof MenuColors.SUBMENU | typeof MenuColors.ACTION | "seed-top" | "seed-low" | "seed-zero";
|
|
84
|
+
count: number;
|
|
85
|
+
tooltip?: string;
|
|
86
|
+
badge?: never;
|
|
87
|
+
active?: never;
|
|
88
|
+
_dim?: boolean;
|
|
89
|
+
_south?: boolean;
|
|
90
|
+
icon?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface UploadItem {
|
|
93
|
+
label: string;
|
|
94
|
+
action: string;
|
|
95
|
+
color: typeof MenuColors.UPLOAD;
|
|
96
|
+
badge?: never;
|
|
97
|
+
tooltip?: string;
|
|
98
|
+
active?: never;
|
|
99
|
+
_dim?: boolean;
|
|
100
|
+
_south?: boolean;
|
|
101
|
+
}
|
|
102
|
+
export type MenuItem = SubmenuItem | ChatItem | ToggleItem | ActionItem | StartItem | SeedItem | CountIndicatorButtonItem | UploadItem;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// ── Strict Color Semantics ────────────────────────────────────────────────────
|
|
2
|
+
// RED = Default button, opens submenu/panel/tool/modal
|
|
3
|
+
// BLUE = CHAT — sends a message to Jammy specifically
|
|
4
|
+
// ORANGE = TOGGLE on/off state, also used for UPLOAD
|
|
5
|
+
export const MenuColors = {
|
|
6
|
+
SUBMENU: "red", // Opens submenu/panel/tool/modal
|
|
7
|
+
CHAT: "blue", // Sends chat message to Jammy
|
|
8
|
+
TOGGLE: "orange", // On/off switch
|
|
9
|
+
ACTION: "red", // Immediate UI action without chat message
|
|
10
|
+
START: "green", // Special color for START button on welcome screen
|
|
11
|
+
UPLOAD: "orange", // File upload action - uses Jimbo orange
|
|
12
|
+
};
|
package/dist/ui/panel.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare const JimboPanel: React.MemoExoticComponent<({ children, classNam
|
|
|
8
8
|
export interface JimboInnerPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
9
|
}
|
|
10
10
|
export declare const JimboInnerPanel: React.MemoExoticComponent<({ children, className, style, ...props }: JimboInnerPanelProps) => import("react/jsx-runtime").JSX.Element>;
|
|
11
|
-
export type JimboTone = 'orange' | 'red' | 'blue' | 'green' | 'tarot' | 'planet' | 'spectral' | 'grey'
|
|
11
|
+
export type JimboTone = 'orange' | 'red' | 'blue' | 'green' | 'tarot' | 'planet' | 'spectral' | 'grey';
|
|
12
12
|
export interface JimboButtonProps {
|
|
13
13
|
tone?: JimboTone;
|
|
14
14
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
package/dist/ui/panel.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, memo } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import { useSway, useDOMMagneticTilt } from './hooks.js';
|
|
4
|
+
import { useSway } from './hooks.js';
|
|
6
5
|
import { JimboText } from './jimboText.js';
|
|
7
6
|
export const JimboPanel = memo(({ children, className = '', sway = false, onBack, hideBack = false, style, ...props }) => {
|
|
8
7
|
const panelRef = useSway(sway);
|
|
@@ -11,30 +10,13 @@ export const JimboPanel = memo(({ children, className = '', sway = false, onBack
|
|
|
11
10
|
JimboPanel.displayName = 'JimboPanel';
|
|
12
11
|
export const JimboInnerPanel = memo(({ children, className = '', style, ...props }) => (_jsx("div", { className: `j-inner-panel ${className}`, style: style, ...props, children: children })));
|
|
13
12
|
JimboInnerPanel.displayName = 'JimboInnerPanel';
|
|
14
|
-
// ─── JimboButton ──────────────────────────────────────────────────────────────
|
|
15
|
-
// Canonical flat 2D Balatro-style button.
|
|
16
|
-
// Two-layer: separate shadow div (3px south + 1px east) that disappears on press.
|
|
17
|
-
// Press translates the face onto the shadow. No gradients, no hover color change.
|
|
18
|
-
const JIMBO_TONE_PAIRS = {
|
|
19
|
-
orange: [JimboColorOption.ORANGE, JimboColorOption.DARK_ORANGE],
|
|
20
|
-
red: [JimboColorOption.RED, JimboColorOption.DARK_RED],
|
|
21
|
-
blue: [JimboColorOption.BLUE, JimboColorOption.DARK_BLUE],
|
|
22
|
-
green: [JimboColorOption.GREEN, JimboColorOption.DARK_GREEN],
|
|
23
|
-
tarot: ['#9e74ce', '#5e437e'],
|
|
24
|
-
planet: ['#00a7ca', '#00657c'],
|
|
25
|
-
spectral: ['#2e76fd', '#14449e'],
|
|
26
|
-
grey: ['#888888', '#555555'],
|
|
27
|
-
gold: ['#f1c40f', '#d35400'],
|
|
28
|
-
};
|
|
29
13
|
export function JimboButton({ tone = 'orange', size = 'md', fullWidth = false, disabled = false, uppercase = false, onClick, style, className = '', children, }) {
|
|
30
14
|
const [pressed, setPressed] = useState(false);
|
|
31
|
-
const [fg, sh] = JIMBO_TONE_PAIRS[tone] ?? JIMBO_TONE_PAIRS.orange;
|
|
32
15
|
const textSize = size === 'xs' ? 'xs' : size === 'sm' ? 'sm' : size === 'lg' ? 'lg' : 'md';
|
|
33
|
-
const { handlers, tiltStyle } = useDOMMagneticTilt(!disabled);
|
|
34
16
|
return (_jsx("div", { className: `j-btn j-btn--${tone} j-btn--${size} ${fullWidth ? 'j-btn--full' : ''} ${disabled ? 'j-btn--disabled' : ''} ${className}`, "data-pressed": pressed, onMouseDown: () => { if (!disabled)
|
|
35
|
-
setPressed(true); }, onMouseUp: () => setPressed(false), onMouseLeave: (
|
|
17
|
+
setPressed(true); }, onMouseUp: () => setPressed(false), onMouseLeave: () => setPressed(false), onTouchStart: () => { if (!disabled)
|
|
36
18
|
setPressed(true); }, onTouchEnd: () => setPressed(false), onClick: () => { if (!disabled)
|
|
37
|
-
onClick?.(); },
|
|
19
|
+
onClick?.(); }, style: style, children: _jsx("div", { className: "j-btn__face", children: _jsx(JimboText, { size: textSize, uppercase: uppercase, children: children }) }) }));
|
|
38
20
|
}
|
|
39
21
|
export function JimboBackButton({ onClick }) {
|
|
40
22
|
return (_jsx("div", { className: "j-flex j-justify-center j-w-full", style: { padding: '4px 0' }, children: _jsx(JimboButton, { tone: "orange", size: "md", fullWidth: true, onClick: onClick, children: "Back" }) }));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./radial-navigation.css";
|
|
2
|
+
import type { RadialButtonColor } from "./RadialButton";
|
|
3
|
+
export type RadialBadgeState = "loading" | "success" | "error" | "info" | "warning";
|
|
4
|
+
export interface RadialBadgeProps {
|
|
5
|
+
label: string;
|
|
6
|
+
state: RadialBadgeState;
|
|
7
|
+
color?: RadialButtonColor;
|
|
8
|
+
tooltip?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Display-only radial status indicator.
|
|
13
|
+
*
|
|
14
|
+
* Visually matches RadialButton but rendered as a `<div>` with no click handler.
|
|
15
|
+
* Shows a status dot (green/red/grey) + label text.
|
|
16
|
+
*/
|
|
17
|
+
export declare function RadialBadge({ label, state, color, tooltip, className }: RadialBadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
5
|
+
import { JimboColorOption } from "../tokens";
|
|
6
|
+
import "./radial-navigation.css";
|
|
7
|
+
import { BUTTON_THEMES } from "./RadialButton";
|
|
8
|
+
const INDICATOR_STYLES = {
|
|
9
|
+
success: { bg: JimboColorOption.GREEN_TEXT, border: JimboColorOption.BORDER_SILVER },
|
|
10
|
+
error: { bg: JimboColorOption.DARK_RED, border: JimboColorOption.DARK_GREY },
|
|
11
|
+
loading: { bg: JimboColorOption.GREY, border: JimboColorOption.DARK_GREY },
|
|
12
|
+
info: { bg: JimboColorOption.BLUE, border: JimboColorOption.DARK_GREY },
|
|
13
|
+
warning: { bg: JimboColorOption.ORANGE, border: JimboColorOption.DARK_GREY },
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Display-only radial status indicator.
|
|
17
|
+
*
|
|
18
|
+
* Visually matches RadialButton but rendered as a `<div>` with no click handler.
|
|
19
|
+
* Shows a status dot (green/red/grey) + label text.
|
|
20
|
+
*/
|
|
21
|
+
export function RadialBadge({ label, state, color = "red", tooltip, className }) {
|
|
22
|
+
const theme = BUTTON_THEMES[color];
|
|
23
|
+
const altText = tooltip ?? `badge: ${label}`;
|
|
24
|
+
return (_jsxs("div", { role: "status", "aria-label": altText, title: altText, className: twMerge(clsx("flex min-w-[60px] items-center justify-center gap-1.5 rounded-[11px] px-[10px] py-[2px] select-none sm:min-w-[64px] sm:px-[14px]", "text-[11.5px] font-normal", "shadow-[var(--btn-shadow)]", className)), style: {
|
|
25
|
+
backgroundColor: theme.bg,
|
|
26
|
+
border: "none",
|
|
27
|
+
height: 24,
|
|
28
|
+
"--btn-shadow": `0 4px 0 0 ${theme.shadow}`,
|
|
29
|
+
}, children: [_jsx("div", { className: twMerge("h-[9px] w-[9px] shrink-0 rounded-full border", (state === "error" || state === "loading") && "animate-pulse"), style: {
|
|
30
|
+
backgroundColor: INDICATOR_STYLES[state].bg,
|
|
31
|
+
borderWidth: 1,
|
|
32
|
+
borderColor: INDICATOR_STYLES[state].border,
|
|
33
|
+
boxShadow: state === "success"
|
|
34
|
+
? "0 0 8px rgba(53,189,134,0.5)"
|
|
35
|
+
: state === "error"
|
|
36
|
+
? "0 0 8px rgba(160,39,33,0.5)"
|
|
37
|
+
: state === "info"
|
|
38
|
+
? "0 0 8px rgba(62,176,248,0.5)"
|
|
39
|
+
: state === "warning"
|
|
40
|
+
? "0 0 8px rgba(254,166,42,0.5)"
|
|
41
|
+
: "none",
|
|
42
|
+
} }), _jsx("span", { className: "jimbo-radial-label jimbo-radial-label--badge", children: label })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RadialBreadcrumbProps {
|
|
2
|
+
label: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Breadcrumb nav pill — "currently viewing" indicator.
|
|
8
|
+
*
|
|
9
|
+
* Sits above the Back button to show the user's position in the menu tree.
|
|
10
|
+
* Non-interactive, dark styling, same pill family as orbital buttons.
|
|
11
|
+
*/
|
|
12
|
+
export declare function RadialBreadcrumb({ label, title, className }: RadialBreadcrumbProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
const BREADCRUMB_STYLE = {
|
|
5
|
+
backgroundColor: "#1a1e2e",
|
|
6
|
+
border: "1.5px solid rgba(255,255,255,0.18)",
|
|
7
|
+
color: "#f6f0d5",
|
|
8
|
+
opacity: 0.92,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Breadcrumb nav pill — "currently viewing" indicator.
|
|
12
|
+
*
|
|
13
|
+
* Sits above the Back button to show the user's position in the menu tree.
|
|
14
|
+
* Non-interactive, dark styling, same pill family as orbital buttons.
|
|
15
|
+
*/
|
|
16
|
+
export function RadialBreadcrumb({ label, title, className }) {
|
|
17
|
+
return (_jsxs("div", { role: "status", "aria-label": title ?? `In: ${label}`, title: title ?? `In: ${label}`, className: twMerge("pointer-events-auto flex items-center gap-1 rounded-[11px] px-[10px] py-[2px] font-serif text-[11.5px] font-normal shadow-[0_4px_0_0_rgba(30,30,30,0.9)] select-none sm:px-[14px]", className), style: BREADCRUMB_STYLE, children: [_jsx("span", { className: "text-[9.5px] opacity-50", children: "\u203A" }), _jsx("span", { className: "whitespace-nowrap", children: label })] }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import "./radial-navigation.css";
|
|
3
|
+
export type RadialButtonColor = "orange" | "purple" | "red" | "green" | "blue" | "seed-zero" | "seed-low" | "seed-top";
|
|
4
|
+
interface ButtonTheme {
|
|
5
|
+
bg: string;
|
|
6
|
+
shadow: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
declare const BUTTON_THEMES: Record<RadialButtonColor, ButtonTheme>;
|
|
10
|
+
export interface RadialButtonActionProps {
|
|
11
|
+
variant?: "action";
|
|
12
|
+
label: string;
|
|
13
|
+
color?: RadialButtonColor;
|
|
14
|
+
tooltip?: string;
|
|
15
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
export interface RadialButtonToggleProps {
|
|
20
|
+
variant: "toggle";
|
|
21
|
+
label: string;
|
|
22
|
+
active: boolean;
|
|
23
|
+
color?: RadialButtonColor;
|
|
24
|
+
tooltip?: string;
|
|
25
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
26
|
+
className?: string;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}
|
|
30
|
+
export interface RadialButtonCountProps {
|
|
31
|
+
variant: "count";
|
|
32
|
+
label: string;
|
|
33
|
+
count: number;
|
|
34
|
+
icon?: string;
|
|
35
|
+
color?: RadialButtonColor;
|
|
36
|
+
tooltip?: string;
|
|
37
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
38
|
+
className?: string;
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
}
|
|
41
|
+
export interface RadialButtonBackProps {
|
|
42
|
+
variant: "back" | "start";
|
|
43
|
+
label: string;
|
|
44
|
+
color?: RadialButtonColor;
|
|
45
|
+
tooltip?: string;
|
|
46
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
47
|
+
className?: string;
|
|
48
|
+
style?: React.CSSProperties;
|
|
49
|
+
}
|
|
50
|
+
export type RadialButtonProps = RadialButtonActionProps | RadialButtonToggleProps | RadialButtonCountProps | RadialButtonBackProps;
|
|
51
|
+
/**
|
|
52
|
+
* Polymorphic radial navigation button.
|
|
53
|
+
*
|
|
54
|
+
* Replaces four separate components (Button, CountIndicator, Toggle, Back/Start)
|
|
55
|
+
* with a single component using a `variant` prop.
|
|
56
|
+
*
|
|
57
|
+
* All variants share the Balatro-styled pill: 3D bottom-edge shadow, press-down on active.
|
|
58
|
+
*/
|
|
59
|
+
export declare function RadialButton(props: RadialButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
export { BUTTON_THEMES };
|
|
61
|
+
export type { ButtonTheme };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { twMerge } from "tailwind-merge";
|
|
6
|
+
import { JimboColorOption } from "../tokens";
|
|
7
|
+
import "./radial-navigation.css";
|
|
8
|
+
const BUTTON_THEMES = {
|
|
9
|
+
orange: { bg: JimboColorOption.ORANGE, shadow: JimboColorOption.DARK_ORANGE, text: JimboColorOption.WHITE },
|
|
10
|
+
purple: { bg: JimboColorOption.PURPLE, shadow: JimboColorOption.DARK_PURPLE, text: JimboColorOption.WHITE },
|
|
11
|
+
red: { bg: JimboColorOption.RED, shadow: JimboColorOption.DARK_RED, text: JimboColorOption.WHITE },
|
|
12
|
+
green: { bg: JimboColorOption.GREEN, shadow: JimboColorOption.DARK_GREEN, text: JimboColorOption.WHITE },
|
|
13
|
+
blue: { bg: JimboColorOption.BLUE, shadow: JimboColorOption.DARK_BLUE, text: JimboColorOption.WHITE },
|
|
14
|
+
"seed-zero": { bg: JimboColorOption.BLACK, shadow: "#111111", text: JimboColorOption.WHITE },
|
|
15
|
+
"seed-low": { bg: JimboColorOption.DARK_GREY, shadow: JimboColorOption.DARKEST, text: JimboColorOption.WHITE },
|
|
16
|
+
"seed-top": { bg: JimboColorOption.DARKEST, shadow: JimboColorOption.BLACK, text: JimboColorOption.GOLD },
|
|
17
|
+
};
|
|
18
|
+
// ── Shared base classes ───────────────────────────────────────────────────────
|
|
19
|
+
const BUTTON_SHADOW = "0 4px 0 0 rgba(0,0,0,0.8)";
|
|
20
|
+
const BASE_CLASSES = clsx("group flex items-center justify-center gap-1.5 rounded-[10px] border-none px-[10px] py-[3px] outline-none select-none", "font-normal", "transition-[transform,box-shadow] duration-[80ms] ease-out");
|
|
21
|
+
const INTERACTIVE_CLASSES = "cursor-pointer";
|
|
22
|
+
const DISABLED_CLASSES = "cursor-not-allowed opacity-67";
|
|
23
|
+
const WIDE_CLASSES = "w-[130px] sm:w-[160px]";
|
|
24
|
+
const NORMAL_WIDTH = "min-w-[54px] sm:min-w-[64px]";
|
|
25
|
+
/**
|
|
26
|
+
* Polymorphic radial navigation button.
|
|
27
|
+
*
|
|
28
|
+
* Replaces four separate components (Button, CountIndicator, Toggle, Back/Start)
|
|
29
|
+
* with a single component using a `variant` prop.
|
|
30
|
+
*
|
|
31
|
+
* All variants share the Balatro-styled pill: 3D bottom-edge shadow, press-down on active.
|
|
32
|
+
*/
|
|
33
|
+
export function RadialButton(props) {
|
|
34
|
+
const { label, tooltip, onClick, className } = props;
|
|
35
|
+
const variant = props.variant ?? "action";
|
|
36
|
+
const [isPressed, setIsPressed] = useState(false);
|
|
37
|
+
// South pills (Start / Back): same Balatro orange as Back — never green-vs-red split.
|
|
38
|
+
const isSouthPill = variant === "start" || variant === "back";
|
|
39
|
+
let colorKey;
|
|
40
|
+
if (isSouthPill) {
|
|
41
|
+
colorKey = props.color ?? "orange";
|
|
42
|
+
}
|
|
43
|
+
else if (variant === "toggle") {
|
|
44
|
+
colorKey = props.color ?? "red";
|
|
45
|
+
}
|
|
46
|
+
else if (variant === "count") {
|
|
47
|
+
colorKey = props.color ?? "red";
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
colorKey = props.color ?? "red";
|
|
51
|
+
}
|
|
52
|
+
const theme = BUTTON_THEMES[colorKey];
|
|
53
|
+
const altText = _computeAltText(props, variant);
|
|
54
|
+
const isDisabled = variant === "toggle" && props.disabled === true;
|
|
55
|
+
const isWide = variant === "back" || variant === "start";
|
|
56
|
+
return (_jsxs("button", { type: "button", onClick: isDisabled ? undefined : onClick, onPointerDown: isDisabled
|
|
57
|
+
? undefined
|
|
58
|
+
: (e) => {
|
|
59
|
+
setIsPressed(true);
|
|
60
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
61
|
+
}, onPointerUp: isDisabled ? undefined : () => setIsPressed(false), onPointerCancel: isDisabled ? undefined : () => setIsPressed(false), onPointerLeave: isDisabled ? undefined : () => setIsPressed(false), "aria-label": altText, title: tooltip ?? altText, disabled: isDisabled, className: twMerge(clsx(BASE_CLASSES, isWide ? WIDE_CLASSES : NORMAL_WIDTH, isDisabled ? DISABLED_CLASSES : INTERACTIVE_CLASSES, className)), style: {
|
|
62
|
+
backgroundColor: theme.bg,
|
|
63
|
+
boxShadow: isPressed && !isDisabled ? "none" : BUTTON_SHADOW,
|
|
64
|
+
transform: isPressed && !isDisabled ? "translateY(4px)" : "translateY(0)",
|
|
65
|
+
...(props.style ?? {}),
|
|
66
|
+
}, children: [variant === "toggle" && _jsx(ToggleDot, { active: props.active, disabled: isDisabled }), _jsx("span", { className: "jimbo-radial-label jimbo-radial-label--action", children: label }), variant === "count" && _jsx(CountBadge, { count: props.count, icon: props.icon })] }));
|
|
67
|
+
}
|
|
68
|
+
// ── Internal sub-components ───────────────────────────────────────────────────
|
|
69
|
+
function ToggleDot({ active, disabled: _disabled }) {
|
|
70
|
+
const dotStyle = active
|
|
71
|
+
? { backgroundColor: JimboColorOption.GREEN_TEXT, borderColor: JimboColorOption.BORDER_SILVER }
|
|
72
|
+
: { backgroundColor: JimboColorOption.DARK_RED, borderColor: JimboColorOption.DARK_GREY };
|
|
73
|
+
return (_jsx("div", { className: twMerge("h-[9px] w-[9px] shrink-0 rounded-full border"), style: { borderWidth: 1, ...dotStyle } }));
|
|
74
|
+
}
|
|
75
|
+
function CountBadge({ count, icon }) {
|
|
76
|
+
if (icon) {
|
|
77
|
+
return (_jsx("span", { className: "inline-flex items-center justify-center text-[10px] leading-none", style: { marginLeft: 2 }, "aria-hidden": "true", children: icon }));
|
|
78
|
+
}
|
|
79
|
+
return (_jsx("span", { className: "inline-flex items-center justify-center rounded-full font-mono leading-none", style: {
|
|
80
|
+
fontSize: "10px",
|
|
81
|
+
backgroundColor: JimboColorOption.DARKEST,
|
|
82
|
+
color: count > 0 ? JimboColorOption.GOLD : JimboColorOption.GREY,
|
|
83
|
+
minWidth: 16,
|
|
84
|
+
height: 16,
|
|
85
|
+
padding: "2px 4px",
|
|
86
|
+
marginLeft: 2,
|
|
87
|
+
}, children: count }));
|
|
88
|
+
}
|
|
89
|
+
function _computeAltText(props, variant) {
|
|
90
|
+
if (props.tooltip)
|
|
91
|
+
return props.tooltip;
|
|
92
|
+
switch (variant) {
|
|
93
|
+
case "toggle":
|
|
94
|
+
return `toggle: ${props.label} (${props.active ? "on" : "off"})`;
|
|
95
|
+
case "count":
|
|
96
|
+
return `${props.label}: ${props.icon ?? props.count}`;
|
|
97
|
+
default:
|
|
98
|
+
return `button: ${props.label}`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Re-export themes for badge usage
|
|
102
|
+
export { BUTTON_THEMES };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { MenuItem } from "../mascot/menuConfig";
|
|
3
|
+
export interface RadialMenuProps {
|
|
4
|
+
/** Menu items to display. The item flagged _south is always pinned at the bottom. */
|
|
5
|
+
items: MenuItem[];
|
|
6
|
+
/** When true, pills animate out to center. */
|
|
7
|
+
showClosing: boolean;
|
|
8
|
+
/** Jammy hit target / image size (px) — used so pills clear the seed graphic. */
|
|
9
|
+
mascotSizePx: number;
|
|
10
|
+
/** Horizontal radius for the orbit (px). */
|
|
11
|
+
orbitRadiusX: number;
|
|
12
|
+
/** Vertical radius (typically same or slightly larger than horizontal). */
|
|
13
|
+
orbitRadiusY: number;
|
|
14
|
+
/** Vertical translation for keyboard dodge (px). */
|
|
15
|
+
mascotTranslateY: number;
|
|
16
|
+
/** Current menu name — used for stable React keys. */
|
|
17
|
+
currentMenu: string;
|
|
18
|
+
/** Callback when a menu item is clicked. */
|
|
19
|
+
onItemClick: (item: MenuItem) => void;
|
|
20
|
+
/** Callback for the Back button. */
|
|
21
|
+
onBack: () => void;
|
|
22
|
+
/** Optional breadcrumb rendered above the Back button position. */
|
|
23
|
+
breadcrumb?: ReactNode;
|
|
24
|
+
/** Show dedicated page controls near the south back/start button. */
|
|
25
|
+
showPageControls?: boolean;
|
|
26
|
+
/** Go to previous orbital menu page. */
|
|
27
|
+
onPagePrev?: () => void;
|
|
28
|
+
/** Go to next orbital menu page. */
|
|
29
|
+
onPageNext?: () => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Orbital radial menu layout engine.
|
|
33
|
+
*
|
|
34
|
+
* South (`_south`) stays pinned under the mascot. Everyone else sits on an ellipse
|
|
35
|
+
* at equal angle steps, with a single radial push if a pill would overlap the
|
|
36
|
+
* center — no iterative “solver” (that was causing mushy, uneven layouts).
|
|
37
|
+
*/
|
|
38
|
+
export declare function RadialMenu({ items, showClosing, mascotSizePx, orbitRadiusX, orbitRadiusY, mascotTranslateY, currentMenu, onItemClick, onBack, breadcrumb, showPageControls, onPagePrev, onPageNext, }: RadialMenuProps): import("react/jsx-runtime").JSX.Element | null;
|