@luxfi/ui 6.1.3 → 7.1.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/dist/accordion.cjs +213 -0
- package/dist/accordion.d.cts +74 -0
- package/dist/accordion.d.ts +74 -0
- package/dist/accordion.js +186 -0
- package/dist/alert.cjs +430 -0
- package/dist/alert.d.cts +30 -0
- package/dist/alert.d.ts +30 -0
- package/dist/alert.js +408 -0
- package/dist/avatar.cjs +149 -0
- package/dist/avatar.d.cts +25 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +125 -0
- package/dist/badge.cjs +452 -0
- package/dist/badge.d.cts +19 -0
- package/dist/badge.d.ts +19 -0
- package/dist/badge.js +431 -0
- package/dist/bank.cjs +272 -0
- package/dist/bank.d.cts +76 -0
- package/dist/bank.d.ts +76 -0
- package/dist/bank.js +258 -0
- package/dist/button.cjs +578 -0
- package/dist/button.d.cts +55 -0
- package/dist/button.d.ts +55 -0
- package/dist/button.js +553 -0
- package/dist/checkbox.cjs +265 -0
- package/dist/checkbox.d.cts +31 -0
- package/dist/checkbox.d.ts +31 -0
- package/dist/checkbox.js +241 -0
- package/dist/close-button.cjs +92 -0
- package/dist/close-button.d.cts +9 -0
- package/dist/close-button.d.ts +9 -0
- package/dist/close-button.js +70 -0
- package/dist/collapsible.cjs +555 -0
- package/dist/collapsible.d.cts +23 -0
- package/dist/collapsible.d.ts +23 -0
- package/dist/collapsible.js +532 -0
- package/dist/color-mode.cjs +96 -0
- package/dist/color-mode.d.cts +25 -0
- package/dist/color-mode.d.ts +25 -0
- package/dist/color-mode.js +72 -0
- package/dist/dialog.cjs +295 -0
- package/dist/dialog.d.cts +65 -0
- package/dist/dialog.d.ts +65 -0
- package/dist/dialog.js +263 -0
- package/dist/drawer.cjs +231 -0
- package/dist/drawer.d.cts +36 -0
- package/dist/drawer.d.ts +36 -0
- package/dist/drawer.js +199 -0
- package/dist/empty-state.cjs +93 -0
- package/dist/empty-state.d.cts +13 -0
- package/dist/empty-state.d.ts +13 -0
- package/dist/empty-state.js +71 -0
- package/dist/expiration-selector.cjs +138 -0
- package/dist/expiration-selector.d.cts +16 -0
- package/dist/expiration-selector.d.ts +16 -0
- package/dist/expiration-selector.js +116 -0
- package/dist/field.cjs +183 -0
- package/dist/field.d.cts +24 -0
- package/dist/field.d.ts +24 -0
- package/dist/field.js +160 -0
- package/dist/greeks-display.cjs +114 -0
- package/dist/greeks-display.d.cts +14 -0
- package/dist/greeks-display.d.ts +14 -0
- package/dist/greeks-display.js +92 -0
- package/dist/heading.cjs +41 -0
- package/dist/heading.d.cts +10 -0
- package/dist/heading.d.ts +10 -0
- package/dist/heading.js +35 -0
- package/dist/icon-button.cjs +367 -0
- package/dist/icon-button.d.cts +33 -0
- package/dist/icon-button.d.ts +33 -0
- package/dist/icon-button.js +345 -0
- package/dist/image.cjs +425 -0
- package/dist/image.d.cts +57 -0
- package/dist/image.d.ts +57 -0
- package/dist/image.js +404 -0
- package/dist/index.cjs +7220 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +7043 -0
- package/dist/input-group.cjs +155 -0
- package/dist/input-group.d.cts +17 -0
- package/dist/input-group.d.ts +17 -0
- package/dist/input-group.js +133 -0
- package/dist/input.cjs +65 -0
- package/dist/input.d.cts +11 -0
- package/dist/input.d.ts +11 -0
- package/dist/input.js +59 -0
- package/dist/link.cjs +483 -0
- package/dist/link.d.cts +37 -0
- package/dist/link.d.ts +37 -0
- package/dist/link.js +459 -0
- package/dist/menu.cjs +305 -0
- package/dist/menu.d.cts +84 -0
- package/dist/menu.d.ts +84 -0
- package/dist/menu.js +269 -0
- package/dist/option-chain.cjs +221 -0
- package/dist/option-chain.d.cts +37 -0
- package/dist/option-chain.d.ts +37 -0
- package/dist/option-chain.js +199 -0
- package/dist/option-position.cjs +164 -0
- package/dist/option-position.d.cts +29 -0
- package/dist/option-position.d.ts +29 -0
- package/dist/option-position.js +142 -0
- package/dist/pin-input.cjs +182 -0
- package/dist/pin-input.d.cts +25 -0
- package/dist/pin-input.d.ts +25 -0
- package/dist/pin-input.js +160 -0
- package/dist/pnl-diagram.cjs +453 -0
- package/dist/pnl-diagram.d.cts +11 -0
- package/dist/pnl-diagram.d.ts +11 -0
- package/dist/pnl-diagram.js +431 -0
- package/dist/popover.cjs +342 -0
- package/dist/popover.d.cts +73 -0
- package/dist/popover.d.ts +73 -0
- package/dist/popover.js +310 -0
- package/dist/progress-circle.cjs +152 -0
- package/dist/progress-circle.d.cts +47 -0
- package/dist/progress-circle.d.ts +47 -0
- package/dist/progress-circle.js +128 -0
- package/dist/progress.cjs +117 -0
- package/dist/progress.d.cts +28 -0
- package/dist/progress.d.ts +28 -0
- package/dist/progress.js +94 -0
- package/dist/provider.cjs +62 -0
- package/dist/provider.d.cts +7 -0
- package/dist/provider.d.ts +7 -0
- package/dist/provider.js +40 -0
- package/dist/radio.cjs +177 -0
- package/dist/radio.d.cts +53 -0
- package/dist/radio.d.ts +53 -0
- package/dist/radio.js +153 -0
- package/dist/rating.cjs +80 -0
- package/dist/rating.d.cts +14 -0
- package/dist/rating.d.ts +14 -0
- package/dist/rating.js +58 -0
- package/dist/select.cjs +644 -0
- package/dist/select.d.cts +130 -0
- package/dist/select.d.ts +130 -0
- package/dist/select.js +610 -0
- package/dist/separator.cjs +57 -0
- package/dist/separator.d.cts +13 -0
- package/dist/separator.d.ts +13 -0
- package/dist/separator.js +51 -0
- package/dist/skeleton.cjs +223 -0
- package/dist/skeleton.d.cts +60 -0
- package/dist/skeleton.d.ts +60 -0
- package/dist/skeleton.js +199 -0
- package/dist/slider.cjs +138 -0
- package/dist/slider.d.cts +24 -0
- package/dist/slider.d.ts +24 -0
- package/dist/slider.js +115 -0
- package/dist/strategy-builder.cjs +373 -0
- package/dist/strategy-builder.d.cts +26 -0
- package/dist/strategy-builder.d.ts +26 -0
- package/dist/strategy-builder.js +351 -0
- package/dist/switch.cjs +163 -0
- package/dist/switch.d.cts +27 -0
- package/dist/switch.d.ts +27 -0
- package/dist/switch.js +140 -0
- package/dist/table.cjs +897 -0
- package/dist/table.d.cts +107 -0
- package/dist/table.d.ts +107 -0
- package/dist/table.js +866 -0
- package/dist/tabs.cjs +240 -0
- package/dist/tabs.d.cts +78 -0
- package/dist/tabs.d.ts +78 -0
- package/dist/tabs.js +213 -0
- package/dist/tag.cjs +529 -0
- package/dist/tag.d.cts +21 -0
- package/dist/tag.d.ts +21 -0
- package/dist/tag.js +507 -0
- package/dist/textarea.cjs +65 -0
- package/dist/textarea.d.cts +11 -0
- package/dist/textarea.d.ts +11 -0
- package/dist/textarea.js +59 -0
- package/dist/toaster.cjs +99 -0
- package/dist/toaster.d.cts +29 -0
- package/dist/toaster.d.ts +29 -0
- package/dist/toaster.js +96 -0
- package/dist/tooltip.cjs +172 -0
- package/dist/tooltip.d.cts +38 -0
- package/dist/tooltip.d.ts +38 -0
- package/dist/tooltip.js +150 -0
- package/dist/utils.cjs +11 -0
- package/dist/utils.d.cts +5 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +9 -0
- package/package.json +284 -89
- package/src/accordion.tsx +285 -0
- package/src/alert.tsx +221 -0
- package/src/avatar.tsx +174 -0
- package/src/badge.tsx +147 -0
- package/src/bank.tsx +325 -0
- package/src/button.tsx +477 -0
- package/src/checkbox.tsx +307 -0
- package/src/close-button.tsx +77 -0
- package/src/collapsible.tsx +126 -0
- package/src/color-mode.tsx +125 -0
- package/src/dialog.tsx +346 -0
- package/src/drawer.tsx +186 -0
- package/src/empty-state.tsx +97 -0
- package/src/expiration-selector.tsx +163 -0
- package/src/field.tsx +202 -0
- package/src/greeks-display.tsx +138 -0
- package/src/heading.tsx +44 -0
- package/src/icon-button.tsx +217 -0
- package/src/image.tsx +280 -0
- package/src/index.ts +267 -138
- package/src/input-group.tsx +159 -0
- package/src/input.tsx +60 -0
- package/src/link.tsx +326 -0
- package/src/menu.tsx +471 -0
- package/src/option-chain.tsx +261 -0
- package/src/option-position.tsx +200 -0
- package/src/pin-input.tsx +187 -0
- package/src/pnl-diagram.tsx +458 -0
- package/src/popover.tsx +386 -0
- package/src/progress-circle.tsx +180 -0
- package/src/progress.tsx +109 -0
- package/src/provider.tsx +12 -0
- package/src/radio.tsx +175 -0
- package/src/rating.tsx +79 -0
- package/src/select.tsx +696 -0
- package/src/separator.tsx +59 -0
- package/src/skeleton.tsx +254 -0
- package/src/slider.tsx +152 -0
- package/src/strategy-builder.tsx +466 -0
- package/src/switch.tsx +158 -0
- package/src/table.tsx +621 -0
- package/src/tabs.tsx +354 -0
- package/src/tag.tsx +159 -0
- package/src/textarea.tsx +60 -0
- package/src/toaster.tsx +117 -0
- package/src/tokens.css +438 -0
- package/src/tooltip.tsx +191 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils.ts +6 -0
- package/tokens.css +438 -0
- package/LICENSE +0 -122
- package/README.md +0 -109
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from './utils';
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Types
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export interface ExpirationOption {
|
|
12
|
+
date: string;
|
|
13
|
+
dte: number;
|
|
14
|
+
type: 'weekly' | 'monthly' | 'quarterly';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ExpirationSelectorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
18
|
+
readonly expirations: ExpirationOption[];
|
|
19
|
+
readonly selected: string;
|
|
20
|
+
readonly onSelect: (date: string) => void;
|
|
21
|
+
readonly showGroups?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function formatExpDate(date: string): string {
|
|
29
|
+
const d = new Date(date);
|
|
30
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const GROUP_ORDER: ExpirationOption['type'][] = ['weekly', 'monthly', 'quarterly'];
|
|
34
|
+
|
|
35
|
+
const GROUP_LABELS: Record<ExpirationOption['type'], string> = {
|
|
36
|
+
weekly: 'Weekly',
|
|
37
|
+
monthly: 'Monthly',
|
|
38
|
+
quarterly: 'Quarterly',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// ExpirationPill
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
interface ExpirationPillProps {
|
|
46
|
+
readonly exp: ExpirationOption;
|
|
47
|
+
readonly isSelected: boolean;
|
|
48
|
+
readonly onClick: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ExpirationPill({ exp, isSelected, onClick }: ExpirationPillProps) {
|
|
52
|
+
return (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={onClick}
|
|
56
|
+
className={cn(
|
|
57
|
+
'flex flex-col items-center gap-0.5 rounded-md px-3 py-1.5 transition-colors shrink-0',
|
|
58
|
+
'border text-xs',
|
|
59
|
+
isSelected
|
|
60
|
+
? 'border-blue-500/60 bg-blue-900/30 text-blue-300'
|
|
61
|
+
: 'border-zinc-800 bg-zinc-900/50 text-zinc-400 hover:border-zinc-600 hover:text-zinc-200',
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<span className="font-medium whitespace-nowrap">
|
|
65
|
+
{formatExpDate(exp.date)}
|
|
66
|
+
</span>
|
|
67
|
+
<span className={cn(
|
|
68
|
+
'font-mono tabular-nums text-[10px]',
|
|
69
|
+
isSelected ? 'text-blue-400/70' : 'text-zinc-600',
|
|
70
|
+
exp.dte <= 7 && !isSelected && 'text-amber-600',
|
|
71
|
+
)}>
|
|
72
|
+
{exp.dte}d
|
|
73
|
+
</span>
|
|
74
|
+
</button>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// ExpirationSelector
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
export const ExpirationSelector = React.forwardRef<HTMLDivElement, ExpirationSelectorProps>(
|
|
83
|
+
function ExpirationSelector(props, ref) {
|
|
84
|
+
const {
|
|
85
|
+
expirations,
|
|
86
|
+
selected,
|
|
87
|
+
onSelect,
|
|
88
|
+
showGroups = false,
|
|
89
|
+
className,
|
|
90
|
+
...rest
|
|
91
|
+
} = props;
|
|
92
|
+
|
|
93
|
+
const scrollRef = React.useRef<HTMLDivElement>(null);
|
|
94
|
+
|
|
95
|
+
// Scroll selected pill into view on mount / change
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
const container = scrollRef.current;
|
|
98
|
+
if (!container) return;
|
|
99
|
+
const selectedEl = container.querySelector('[data-selected="true"]');
|
|
100
|
+
if (selectedEl) {
|
|
101
|
+
selectedEl.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
|
|
102
|
+
}
|
|
103
|
+
}, [selected]);
|
|
104
|
+
|
|
105
|
+
if (showGroups) {
|
|
106
|
+
const grouped = GROUP_ORDER.map((type) => ({
|
|
107
|
+
type,
|
|
108
|
+
label: GROUP_LABELS[type],
|
|
109
|
+
items: expirations.filter((e) => e.type === type),
|
|
110
|
+
})).filter((g) => g.items.length > 0);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div
|
|
114
|
+
ref={ref}
|
|
115
|
+
className={cn('flex flex-col gap-2', className)}
|
|
116
|
+
{...rest}
|
|
117
|
+
>
|
|
118
|
+
{grouped.map((group) => (
|
|
119
|
+
<div key={group.type} className="flex flex-col gap-1">
|
|
120
|
+
<span className="text-[10px] font-semibold uppercase tracking-wider text-zinc-600 px-1">
|
|
121
|
+
{group.label}
|
|
122
|
+
</span>
|
|
123
|
+
<div className="flex gap-1.5 overflow-x-auto no-scrollbar">
|
|
124
|
+
{group.items.map((exp) => (
|
|
125
|
+
<div key={exp.date} data-selected={exp.date === selected || undefined}>
|
|
126
|
+
<ExpirationPill
|
|
127
|
+
exp={exp}
|
|
128
|
+
isSelected={exp.date === selected}
|
|
129
|
+
onClick={() => onSelect(exp.date)}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
ref={ref}
|
|
143
|
+
className={cn('flex items-center gap-1', className)}
|
|
144
|
+
{...rest}
|
|
145
|
+
>
|
|
146
|
+
<div
|
|
147
|
+
ref={scrollRef}
|
|
148
|
+
className="flex gap-1.5 overflow-x-auto no-scrollbar"
|
|
149
|
+
>
|
|
150
|
+
{expirations.map((exp) => (
|
|
151
|
+
<div key={exp.date} data-selected={exp.date === selected || undefined}>
|
|
152
|
+
<ExpirationPill
|
|
153
|
+
exp={exp}
|
|
154
|
+
isSelected={exp.date === selected}
|
|
155
|
+
onClick={() => onSelect(exp.date)}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
);
|
package/src/field.tsx
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from './utils';
|
|
5
|
+
|
|
6
|
+
import type { InputProps } from './input';
|
|
7
|
+
import type { InputGroupProps } from './input-group';
|
|
8
|
+
|
|
9
|
+
// Inline constants
|
|
10
|
+
const space = String.fromCharCode(32);
|
|
11
|
+
|
|
12
|
+
function getComponentDisplayName(type: string | React.JSXElementConstructor<unknown>): string | undefined {
|
|
13
|
+
if (typeof type === 'string') return undefined;
|
|
14
|
+
if ('displayName' in type) return type.displayName as string;
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface FieldProps {
|
|
19
|
+
readonly label?: React.ReactNode;
|
|
20
|
+
readonly helperText?: React.ReactNode;
|
|
21
|
+
readonly errorText?: React.ReactNode;
|
|
22
|
+
readonly optionalText?: React.ReactNode;
|
|
23
|
+
readonly children: React.ReactElement<InputProps> | React.ReactElement<InputGroupProps>;
|
|
24
|
+
readonly size?: 'sm' | 'md' | 'lg' | '2xl';
|
|
25
|
+
readonly required?: boolean;
|
|
26
|
+
readonly disabled?: boolean;
|
|
27
|
+
readonly readOnly?: boolean;
|
|
28
|
+
readonly invalid?: boolean;
|
|
29
|
+
readonly floating?: boolean;
|
|
30
|
+
readonly bgColor?: string | Record<string, string>;
|
|
31
|
+
readonly id?: string;
|
|
32
|
+
readonly className?: string;
|
|
33
|
+
readonly style?: React.CSSProperties;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
37
|
+
function Field(props, ref) {
|
|
38
|
+
const {
|
|
39
|
+
label,
|
|
40
|
+
children,
|
|
41
|
+
helperText,
|
|
42
|
+
errorText,
|
|
43
|
+
optionalText,
|
|
44
|
+
size,
|
|
45
|
+
required,
|
|
46
|
+
disabled,
|
|
47
|
+
readOnly,
|
|
48
|
+
invalid,
|
|
49
|
+
floating,
|
|
50
|
+
id,
|
|
51
|
+
className,
|
|
52
|
+
style,
|
|
53
|
+
} = props;
|
|
54
|
+
|
|
55
|
+
// A floating field cannot be without a label.
|
|
56
|
+
if (floating && label) {
|
|
57
|
+
const injectedProps = {
|
|
58
|
+
className: 'peer',
|
|
59
|
+
placeholder: ' ',
|
|
60
|
+
size,
|
|
61
|
+
floating,
|
|
62
|
+
disabled,
|
|
63
|
+
readOnly,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const labelElement = (
|
|
67
|
+
<LabelPrimitive.Root
|
|
68
|
+
className={ cn(
|
|
69
|
+
'absolute left-3 top-1/2 -translate-y-1/2',
|
|
70
|
+
'text-[var(--color-input-placeholder,theme(colors.gray.400))]',
|
|
71
|
+
'pointer-events-none select-none',
|
|
72
|
+
'origin-top-left transition-all duration-150',
|
|
73
|
+
'peer-focus:top-1 peer-focus:translate-y-0 peer-focus:scale-75',
|
|
74
|
+
'peer-[:not(:placeholder-shown)]:top-1 peer-[:not(:placeholder-shown)]:translate-y-0 peer-[:not(:placeholder-shown)]:scale-75',
|
|
75
|
+
) }
|
|
76
|
+
htmlFor={ id }
|
|
77
|
+
>
|
|
78
|
+
{ label }
|
|
79
|
+
{ required && (
|
|
80
|
+
<span className="ml-0.5 text-[var(--color-fg-error,theme(colors.red.500))]" aria-hidden="true">*</span>
|
|
81
|
+
) }
|
|
82
|
+
{ !required && optionalText && (
|
|
83
|
+
<span className="ml-1 text-[var(--color-text-secondary,theme(colors.gray.400))] text-sm">
|
|
84
|
+
{ optionalText }
|
|
85
|
+
</span>
|
|
86
|
+
) }
|
|
87
|
+
{ errorText && (
|
|
88
|
+
<span className="ml-0.5 text-[var(--color-fg-error,theme(colors.red.500))] text-sm">
|
|
89
|
+
-{ space }{ errorText }
|
|
90
|
+
</span>
|
|
91
|
+
) }
|
|
92
|
+
</LabelPrimitive.Root>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const helperTextElement = helperText ? (
|
|
96
|
+
<p className="mt-1.5 text-xs text-[var(--color-text-secondary,theme(colors.gray.400))]">
|
|
97
|
+
{ helperText }
|
|
98
|
+
</p>
|
|
99
|
+
) : null;
|
|
100
|
+
|
|
101
|
+
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
|
|
102
|
+
const isInputGroup = getComponentDisplayName(child.type) === 'InputGroup';
|
|
103
|
+
|
|
104
|
+
if (isInputGroup) {
|
|
105
|
+
const inputElement = React.cloneElement(
|
|
106
|
+
React.Children.only<React.ReactElement<InputProps>>(child.props.children as React.ReactElement<InputProps>),
|
|
107
|
+
injectedProps,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const groupInputElement = React.cloneElement(child,
|
|
111
|
+
{},
|
|
112
|
+
inputElement,
|
|
113
|
+
labelElement,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div
|
|
118
|
+
ref={ ref }
|
|
119
|
+
id={ id }
|
|
120
|
+
className={ cn('relative w-full', className) }
|
|
121
|
+
style={ style }
|
|
122
|
+
data-disabled={ disabled || undefined }
|
|
123
|
+
data-readonly={ readOnly || undefined }
|
|
124
|
+
data-invalid={ invalid || undefined }
|
|
125
|
+
>
|
|
126
|
+
{ groupInputElement }
|
|
127
|
+
{ helperTextElement }
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const inputElement = React.cloneElement(child, injectedProps);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
ref={ ref }
|
|
137
|
+
id={ id }
|
|
138
|
+
className={ cn('relative w-full', className) }
|
|
139
|
+
style={ style }
|
|
140
|
+
data-disabled={ disabled || undefined }
|
|
141
|
+
data-readonly={ readOnly || undefined }
|
|
142
|
+
data-invalid={ invalid || undefined }
|
|
143
|
+
>
|
|
144
|
+
{ inputElement }
|
|
145
|
+
{ labelElement }
|
|
146
|
+
{ helperTextElement }
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Non-floating: standard field layout
|
|
152
|
+
const injectedProps = {
|
|
153
|
+
size,
|
|
154
|
+
};
|
|
155
|
+
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
|
|
156
|
+
const clonedChild = React.cloneElement(child, injectedProps);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
ref={ ref }
|
|
161
|
+
id={ id }
|
|
162
|
+
className={ cn('flex flex-col gap-1', className) }
|
|
163
|
+
style={ style }
|
|
164
|
+
data-disabled={ disabled || undefined }
|
|
165
|
+
data-readonly={ readOnly || undefined }
|
|
166
|
+
data-invalid={ invalid || undefined }
|
|
167
|
+
>
|
|
168
|
+
{ label && (
|
|
169
|
+
<LabelPrimitive.Root
|
|
170
|
+
className={ cn(
|
|
171
|
+
'text-sm font-medium text-[var(--color-field-label,theme(colors.gray.700))]',
|
|
172
|
+
'dark:text-[var(--color-field-label,theme(colors.gray.300))]',
|
|
173
|
+
disabled && 'opacity-40',
|
|
174
|
+
) }
|
|
175
|
+
htmlFor={ id }
|
|
176
|
+
>
|
|
177
|
+
{ label }
|
|
178
|
+
{ required && (
|
|
179
|
+
<span className="ml-0.5 text-[var(--color-fg-error,theme(colors.red.500))]" aria-hidden="true">*</span>
|
|
180
|
+
) }
|
|
181
|
+
{ !required && optionalText && (
|
|
182
|
+
<span className="ml-1 text-[var(--color-text-secondary,theme(colors.gray.400))] text-sm font-normal">
|
|
183
|
+
{ optionalText }
|
|
184
|
+
</span>
|
|
185
|
+
) }
|
|
186
|
+
</LabelPrimitive.Root>
|
|
187
|
+
) }
|
|
188
|
+
{ clonedChild }
|
|
189
|
+
{ helperText && (
|
|
190
|
+
<p className="text-xs text-[var(--color-text-secondary,theme(colors.gray.400))]">
|
|
191
|
+
{ helperText }
|
|
192
|
+
</p>
|
|
193
|
+
) }
|
|
194
|
+
{ errorText && (
|
|
195
|
+
<p className="text-xs text-[var(--color-fg-error,theme(colors.red.500))]">
|
|
196
|
+
{ errorText }
|
|
197
|
+
</p>
|
|
198
|
+
) }
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
},
|
|
202
|
+
);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from './utils';
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Types
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export interface GreeksDisplayProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
12
|
+
readonly delta: number;
|
|
13
|
+
readonly gamma: number;
|
|
14
|
+
readonly theta: number;
|
|
15
|
+
readonly vega: number;
|
|
16
|
+
readonly rho?: number;
|
|
17
|
+
readonly iv?: number;
|
|
18
|
+
readonly compact?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Greek definitions
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
interface GreekDef {
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
symbol: string;
|
|
29
|
+
color: string;
|
|
30
|
+
barColor: string;
|
|
31
|
+
decimals: number;
|
|
32
|
+
maxMagnitude: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const GREEKS: GreekDef[] = [
|
|
36
|
+
{ key: 'delta', label: 'Delta', symbol: '\u0394', color: 'text-blue-400', barColor: 'bg-blue-500', decimals: 4, maxMagnitude: 1 },
|
|
37
|
+
{ key: 'gamma', label: 'Gamma', symbol: '\u0393', color: 'text-purple-400', barColor: 'bg-purple-500', decimals: 4, maxMagnitude: 0.1 },
|
|
38
|
+
{ key: 'theta', label: 'Theta', symbol: '\u0398', color: 'text-red-400', barColor: 'bg-red-500', decimals: 4, maxMagnitude: 1 },
|
|
39
|
+
{ key: 'vega', label: 'Vega', symbol: '\u03BD', color: 'text-emerald-400', barColor: 'bg-emerald-500', decimals: 4, maxMagnitude: 1 },
|
|
40
|
+
{ key: 'rho', label: 'Rho', symbol: '\u03C1', color: 'text-orange-400', barColor: 'bg-orange-500', decimals: 4, maxMagnitude: 1 },
|
|
41
|
+
{ key: 'iv', label: 'IV', symbol: '\u03C3', color: 'text-amber-400', barColor: 'bg-amber-500', decimals: 1, maxMagnitude: 100 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// GreekItem
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
interface GreekItemProps {
|
|
49
|
+
readonly def: GreekDef;
|
|
50
|
+
readonly value: number;
|
|
51
|
+
readonly compact?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function GreekItem({ def, value, compact }: GreekItemProps) {
|
|
55
|
+
const magnitude = Math.min(Math.abs(value) / def.maxMagnitude, 1);
|
|
56
|
+
const displayValue = def.key === 'iv'
|
|
57
|
+
? `${(value * 100).toFixed(def.decimals)}%`
|
|
58
|
+
: value.toFixed(def.decimals);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
className={cn(
|
|
63
|
+
'flex items-center gap-1.5',
|
|
64
|
+
compact ? 'min-w-0' : 'min-w-[100px]',
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<span className={cn('text-[10px] font-semibold shrink-0', def.color)}>
|
|
68
|
+
{def.symbol}
|
|
69
|
+
</span>
|
|
70
|
+
{!compact && (
|
|
71
|
+
<span className="text-[10px] text-zinc-500 shrink-0 w-[34px]">
|
|
72
|
+
{def.label}
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
<span className={cn(
|
|
76
|
+
'font-mono tabular-nums text-xs text-zinc-200 shrink-0',
|
|
77
|
+
value < 0 && 'text-red-400',
|
|
78
|
+
)}>
|
|
79
|
+
{displayValue}
|
|
80
|
+
</span>
|
|
81
|
+
{!compact && (
|
|
82
|
+
<div className="flex-1 h-1 rounded-full bg-zinc-800 min-w-[24px] max-w-[48px] overflow-hidden">
|
|
83
|
+
<div
|
|
84
|
+
className={cn('h-full rounded-full transition-all', def.barColor)}
|
|
85
|
+
style={{ width: `${magnitude * 100}%`, opacity: 0.7 }}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// GreeksDisplay
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
export const GreeksDisplay = React.forwardRef<HTMLDivElement, GreeksDisplayProps>(
|
|
98
|
+
function GreeksDisplay(props, ref) {
|
|
99
|
+
const {
|
|
100
|
+
delta,
|
|
101
|
+
gamma,
|
|
102
|
+
theta,
|
|
103
|
+
vega,
|
|
104
|
+
rho,
|
|
105
|
+
iv,
|
|
106
|
+
compact = false,
|
|
107
|
+
className,
|
|
108
|
+
...rest
|
|
109
|
+
} = props;
|
|
110
|
+
|
|
111
|
+
const values: Record<string, number | undefined> = {
|
|
112
|
+
delta, gamma, theta, vega, rho, iv,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const activeGreeks = GREEKS.filter((g) => values[g.key] !== undefined);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
ref={ref}
|
|
120
|
+
className={cn(
|
|
121
|
+
'flex flex-wrap items-center',
|
|
122
|
+
compact ? 'gap-3' : 'gap-4',
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
{...rest}
|
|
126
|
+
>
|
|
127
|
+
{activeGreeks.map((g) => (
|
|
128
|
+
<GreekItem
|
|
129
|
+
key={g.key}
|
|
130
|
+
def={g}
|
|
131
|
+
value={values[g.key] as number}
|
|
132
|
+
compact={compact}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
);
|
package/src/heading.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from './utils';
|
|
4
|
+
|
|
5
|
+
export interface HeadingProps extends React.ComponentPropsWithoutRef<'h1'> {
|
|
6
|
+
level?: '1' | '2' | '3';
|
|
7
|
+
// Legacy Chakra style-prop shims
|
|
8
|
+
mb?: number | string;
|
|
9
|
+
size?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const LEVEL_TAG = { '1': 'h1', '2': 'h2', '3': 'h3' } as const;
|
|
13
|
+
|
|
14
|
+
// base (mobile) + lg (desktop) responsive text styles per level
|
|
15
|
+
//
|
|
16
|
+
// Level 1: 18px/24px font-500 -> 32px/40px font-500 tracking=-0.5px
|
|
17
|
+
// Level 2: 16px/24px font-500 -> 24px/32px font-500
|
|
18
|
+
// Level 3: 14px/20px font-600 -> 18px/24px font-500
|
|
19
|
+
const LEVEL_CLASSES = {
|
|
20
|
+
'1': 'text-[18px] leading-[24px] font-medium lg:text-[32px] lg:leading-[40px] lg:tracking-[-0.5px]',
|
|
21
|
+
'2': 'text-[16px] leading-[24px] font-medium lg:text-[24px] lg:leading-[32px]',
|
|
22
|
+
'3': 'text-[14px] leading-[20px] font-semibold lg:text-[18px] lg:leading-[24px] lg:font-medium',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
const BASE_CLASSES = 'font-heading text-heading';
|
|
26
|
+
|
|
27
|
+
export const Heading = React.forwardRef<HTMLHeadingElement, HeadingProps>(
|
|
28
|
+
function Heading({ level, className, mb, size: _size, style: styleProp, ...rest }, ref) {
|
|
29
|
+
const Tag = level ? LEVEL_TAG[level] : 'h2';
|
|
30
|
+
const levelClasses = level ? LEVEL_CLASSES[level] : undefined;
|
|
31
|
+
const mergedStyle = mb !== undefined
|
|
32
|
+
? { ...styleProp, marginBottom: typeof mb === 'number' ? `${ mb * 4 }px` : mb }
|
|
33
|
+
: styleProp;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Tag
|
|
37
|
+
ref={ ref }
|
|
38
|
+
className={ cn(BASE_CLASSES, levelClasses, className) }
|
|
39
|
+
style={ mergedStyle }
|
|
40
|
+
{ ...rest }
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|