@danielthurau/atlas-labs-codex 0.1.0 → 0.2.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/README.md +11 -10
- package/components/react/Badge/Badge.module.css +0 -1
- package/components/react/Badge/Badge.stories.tsx +0 -1
- package/components/react/Badge/Badge.tsx +2 -8
- package/components/react/Badge/index.ts +0 -1
- package/components/react/Button/Button.module.css +0 -1
- package/components/react/Button/Button.stories.tsx +0 -1
- package/components/react/Button/Button.tsx +2 -16
- package/components/react/Button/index.ts +0 -1
- package/components/react/Card/Card.module.css +0 -1
- package/components/react/Card/Card.stories.tsx +3 -11
- package/components/react/Card/Card.tsx +0 -1
- package/components/react/Card/index.ts +0 -1
- package/components/react/Input/Input.module.css +3 -2
- package/components/react/Input/Input.stories.tsx +2 -11
- package/components/react/Input/Input.tsx +0 -1
- package/components/react/Input/index.ts +0 -1
- package/components/react/Modal/Modal.module.css +3 -2
- package/components/react/Modal/Modal.stories.tsx +4 -6
- package/components/react/Modal/Modal.tsx +4 -23
- package/components/react/Modal/index.ts +0 -1
- package/components/react/RefreshButton/RefreshButton.module.css +11 -6
- package/components/react/RefreshButton/RefreshButton.stories.tsx +0 -1
- package/components/react/RefreshButton/RefreshButton.tsx +10 -18
- package/components/react/RefreshButton/index.ts +0 -1
- package/components/react/Tabs/Tabs.module.css +3 -2
- package/components/react/Tabs/Tabs.stories.tsx +4 -11
- package/components/react/Tabs/Tabs.tsx +1 -6
- package/components/react/Tabs/index.ts +0 -1
- package/components/react/Toast/Toast.module.css +3 -2
- package/components/react/Toast/Toast.stories.tsx +1 -4
- package/components/react/Toast/Toast.tsx +3 -23
- package/components/react/Toast/index.ts +0 -1
- package/components/react/index.ts +1 -8
- package/lib/index.ts +0 -1
- package/lib/utils.ts +4 -5
- package/package.json +8 -2
- package/themes/css/base.css +10 -5
- package/themes/css/reset.css +140 -0
- package/themes/css/theme-dark.css +0 -1
- package/themes/css/theme-light.css +0 -1
package/README.md
CHANGED
|
@@ -20,15 +20,15 @@ Then import components, styles, and utilities:
|
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
22
|
// Import components
|
|
23
|
-
import { Button, Card, Input, Badge, RefreshButton } from
|
|
23
|
+
import { Button, Card, Input, Badge, RefreshButton } from "@atlas-labs/design-codex";
|
|
24
24
|
|
|
25
25
|
// Import utilities
|
|
26
|
-
import { formatRelativeTime, clsx } from
|
|
26
|
+
import { formatRelativeTime, clsx } from "@atlas-labs/design-codex/lib";
|
|
27
27
|
|
|
28
28
|
// Import CSS (in your layout.tsx or entry point)
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
29
|
+
import "@atlas-labs/design-codex/themes/css/base.css";
|
|
30
|
+
import "@atlas-labs/design-codex/themes/css/theme-light.css";
|
|
31
|
+
import "@atlas-labs/design-codex/themes/css/theme-dark.css";
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
### Font Setup
|
|
@@ -37,8 +37,8 @@ Copy the Martian Mono font to your project's public folder, or load it in your C
|
|
|
37
37
|
|
|
38
38
|
```css
|
|
39
39
|
@font-face {
|
|
40
|
-
font-family:
|
|
41
|
-
src: url(
|
|
40
|
+
font-family: "Martian Mono";
|
|
41
|
+
src: url("/fonts/MartianMono-VariableFont_wdth,wght.ttf") format("truetype");
|
|
42
42
|
font-weight: 100 800;
|
|
43
43
|
font-display: swap;
|
|
44
44
|
}
|
|
@@ -121,13 +121,15 @@ The codex supports light and dark themes via CSS custom properties. Theme switch
|
|
|
121
121
|
|
|
122
122
|
```html
|
|
123
123
|
<html data-theme="light">
|
|
124
|
-
<!-- or -->
|
|
125
|
-
<html data-theme="dark">
|
|
124
|
+
<!-- or -->
|
|
125
|
+
<html data-theme="dark"></html>
|
|
126
|
+
</html>
|
|
126
127
|
```
|
|
127
128
|
|
|
128
129
|
## Components
|
|
129
130
|
|
|
130
131
|
All components:
|
|
132
|
+
|
|
131
133
|
- Use tokens exclusively (no hardcoded values)
|
|
132
134
|
- Are built on Radix UI primitives for accessibility
|
|
133
135
|
- Encode taste, not flexibility
|
|
@@ -135,4 +137,3 @@ All components:
|
|
|
135
137
|
## License
|
|
136
138
|
|
|
137
139
|
Private repository. Personal use only.
|
|
138
|
-
|
|
@@ -19,19 +19,14 @@ const badgeVariants = cva(styles.badge, {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
export interface BadgeProps
|
|
22
|
-
extends HTMLAttributes<HTMLSpanElement>,
|
|
23
|
-
VariantProps<typeof badgeVariants> {
|
|
22
|
+
extends HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {
|
|
24
23
|
children: ReactNode;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
|
28
27
|
({ className, variant, children, ...props }, ref) => {
|
|
29
28
|
return (
|
|
30
|
-
<span
|
|
31
|
-
ref={ref}
|
|
32
|
-
className={clsx(badgeVariants({ variant }), className)}
|
|
33
|
-
{...props}
|
|
34
|
-
>
|
|
29
|
+
<span ref={ref} className={clsx(badgeVariants({ variant }), className)} {...props}>
|
|
35
30
|
{children}
|
|
36
31
|
</span>
|
|
37
32
|
);
|
|
@@ -39,4 +34,3 @@ export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
|
|
39
34
|
);
|
|
40
35
|
|
|
41
36
|
Badge.displayName = "Badge";
|
|
42
|
-
|
|
@@ -25,27 +25,14 @@ const buttonVariants = cva(styles.button, {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
export interface ButtonProps
|
|
28
|
-
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
29
|
-
VariantProps<typeof buttonVariants> {
|
|
28
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
30
29
|
children: ReactNode;
|
|
31
30
|
loading?: boolean;
|
|
32
31
|
asChild?: boolean;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
36
|
-
(
|
|
37
|
-
{
|
|
38
|
-
className,
|
|
39
|
-
variant,
|
|
40
|
-
size,
|
|
41
|
-
loading,
|
|
42
|
-
disabled,
|
|
43
|
-
asChild = false,
|
|
44
|
-
children,
|
|
45
|
-
...props
|
|
46
|
-
},
|
|
47
|
-
ref
|
|
48
|
-
) => {
|
|
35
|
+
({ className, variant, size, loading, disabled, asChild = false, children, ...props }, ref) => {
|
|
49
36
|
const Comp = asChild ? Slot : "button";
|
|
50
37
|
|
|
51
38
|
return (
|
|
@@ -70,4 +57,3 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
70
57
|
);
|
|
71
58
|
|
|
72
59
|
Button.displayName = "Button";
|
|
73
|
-
|
|
@@ -25,8 +25,7 @@ export const Default: Story = {
|
|
|
25
25
|
args: {
|
|
26
26
|
children: (
|
|
27
27
|
<p>
|
|
28
|
-
This is a basic card with some content. Cards are used to group related
|
|
29
|
-
content together.
|
|
28
|
+
This is a basic card with some content. Cards are used to group related content together.
|
|
30
29
|
</p>
|
|
31
30
|
),
|
|
32
31
|
},
|
|
@@ -35,11 +34,7 @@ export const Default: Story = {
|
|
|
35
34
|
export const Elevated: Story = {
|
|
36
35
|
args: {
|
|
37
36
|
elevated: true,
|
|
38
|
-
children: (
|
|
39
|
-
<p>
|
|
40
|
-
This card has elevation (shadow) for a more prominent appearance.
|
|
41
|
-
</p>
|
|
42
|
-
),
|
|
37
|
+
children: <p>This card has elevation (shadow) for a more prominent appearance.</p>,
|
|
43
38
|
},
|
|
44
39
|
};
|
|
45
40
|
|
|
@@ -82,9 +77,7 @@ export const Complete: Story = {
|
|
|
82
77
|
<p style={{ marginBottom: "16px" }}>
|
|
83
78
|
Configure your project settings here. Changes will be saved automatically.
|
|
84
79
|
</p>
|
|
85
|
-
<p>
|
|
86
|
-
Last updated: January 1, 2024
|
|
87
|
-
</p>
|
|
80
|
+
<p>Last updated: January 1, 2024</p>
|
|
88
81
|
</CardContent>
|
|
89
82
|
<CardFooter>
|
|
90
83
|
<div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
|
|
@@ -111,4 +104,3 @@ export const PaddingVariants: Story = {
|
|
|
111
104
|
</div>
|
|
112
105
|
),
|
|
113
106
|
};
|
|
114
|
-
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
border-radius: var(--input-radius);
|
|
19
19
|
background-color: var(--color-bg-surface);
|
|
20
20
|
color: var(--color-text-primary);
|
|
21
|
-
transition:
|
|
21
|
+
transition:
|
|
22
|
+
border-color 150ms ease,
|
|
23
|
+
box-shadow 150ms ease;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
.input::placeholder {
|
|
@@ -60,4 +62,3 @@
|
|
|
60
62
|
font-size: var(--text-sm);
|
|
61
63
|
color: var(--color-text-secondary);
|
|
62
64
|
}
|
|
63
|
-
|
|
@@ -71,18 +71,9 @@ export const AllStates: Story = {
|
|
|
71
71
|
render: () => (
|
|
72
72
|
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
73
73
|
<Input label="Default" placeholder="Enter text" />
|
|
74
|
-
<Input
|
|
75
|
-
|
|
76
|
-
placeholder="Enter text"
|
|
77
|
-
helperText="This is helper text"
|
|
78
|
-
/>
|
|
79
|
-
<Input
|
|
80
|
-
label="With Error"
|
|
81
|
-
placeholder="Enter text"
|
|
82
|
-
error="This field is required"
|
|
83
|
-
/>
|
|
74
|
+
<Input label="With Helper" placeholder="Enter text" helperText="This is helper text" />
|
|
75
|
+
<Input label="With Error" placeholder="Enter text" error="This field is required" />
|
|
84
76
|
<Input label="Disabled" placeholder="Enter text" disabled />
|
|
85
77
|
</div>
|
|
86
78
|
),
|
|
87
79
|
};
|
|
88
|
-
|
|
@@ -68,7 +68,9 @@
|
|
|
68
68
|
background: transparent;
|
|
69
69
|
border: none;
|
|
70
70
|
cursor: pointer;
|
|
71
|
-
transition:
|
|
71
|
+
transition:
|
|
72
|
+
background-color 150ms ease,
|
|
73
|
+
color 150ms ease;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
.close:hover {
|
|
@@ -100,4 +102,3 @@
|
|
|
100
102
|
transform: translate(-50%, -50%) scale(1);
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
|
-
|
|
@@ -71,12 +71,11 @@ export const LargeModal: Story = {
|
|
|
71
71
|
children: (
|
|
72
72
|
<div>
|
|
73
73
|
<p style={{ marginBottom: "16px" }}>
|
|
74
|
-
Large modals are suitable for complex content that needs more space,
|
|
75
|
-
|
|
74
|
+
Large modals are suitable for complex content that needs more space, such as detailed
|
|
75
|
+
forms or previews.
|
|
76
76
|
</p>
|
|
77
77
|
<p style={{ marginBottom: "16px" }}>
|
|
78
|
-
However, if your modal content is very complex, consider using a
|
|
79
|
-
dedicated page instead.
|
|
78
|
+
However, if your modal content is very complex, consider using a dedicated page instead.
|
|
80
79
|
</p>
|
|
81
80
|
<div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
|
|
82
81
|
<Button variant="secondary">Cancel</Button>
|
|
@@ -101,7 +100,7 @@ const ControlledExample = () => {
|
|
|
101
100
|
description="This modal is controlled via state."
|
|
102
101
|
>
|
|
103
102
|
<p style={{ marginBottom: "16px" }}>
|
|
104
|
-
This modal
|
|
103
|
+
This modal's open state is controlled by the parent component.
|
|
105
104
|
</p>
|
|
106
105
|
<div style={{ display: "flex", gap: "8px", justifyContent: "flex-end" }}>
|
|
107
106
|
<Button variant="secondary" onClick={() => setOpen(false)}>
|
|
@@ -116,4 +115,3 @@ const ControlledExample = () => {
|
|
|
116
115
|
export const Controlled: Story = {
|
|
117
116
|
render: () => <ControlledExample />,
|
|
118
117
|
};
|
|
119
|
-
|
|
@@ -16,34 +16,16 @@ export interface ModalProps {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|
19
|
-
(
|
|
20
|
-
{
|
|
21
|
-
open,
|
|
22
|
-
onOpenChange,
|
|
23
|
-
trigger,
|
|
24
|
-
title,
|
|
25
|
-
description,
|
|
26
|
-
children,
|
|
27
|
-
size = "md",
|
|
28
|
-
},
|
|
29
|
-
ref
|
|
30
|
-
) => {
|
|
19
|
+
({ open, onOpenChange, trigger, title, description, children, size = "md" }, ref) => {
|
|
31
20
|
return (
|
|
32
21
|
<Dialog.Root open={open} onOpenChange={onOpenChange}>
|
|
33
22
|
{trigger && <Dialog.Trigger asChild>{trigger}</Dialog.Trigger>}
|
|
34
23
|
<Dialog.Portal>
|
|
35
24
|
<Dialog.Overlay className={styles.overlay} />
|
|
36
|
-
<Dialog.Content
|
|
37
|
-
|
|
38
|
-
className={clsx(styles.content, styles[size])}
|
|
39
|
-
>
|
|
40
|
-
{title && (
|
|
41
|
-
<Dialog.Title className={styles.title}>{title}</Dialog.Title>
|
|
42
|
-
)}
|
|
25
|
+
<Dialog.Content ref={ref} className={clsx(styles.content, styles[size])}>
|
|
26
|
+
{title && <Dialog.Title className={styles.title}>{title}</Dialog.Title>}
|
|
43
27
|
{description && (
|
|
44
|
-
<Dialog.Description className={styles.description}>
|
|
45
|
-
{description}
|
|
46
|
-
</Dialog.Description>
|
|
28
|
+
<Dialog.Description className={styles.description}>{description}</Dialog.Description>
|
|
47
29
|
)}
|
|
48
30
|
{children}
|
|
49
31
|
<Dialog.Close asChild>
|
|
@@ -72,4 +54,3 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|
|
72
54
|
);
|
|
73
55
|
|
|
74
56
|
Modal.displayName = "Modal";
|
|
75
|
-
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
font-size: var(--text-sm);
|
|
29
29
|
color: var(--color-text-primary);
|
|
30
30
|
cursor: pointer;
|
|
31
|
-
transition:
|
|
31
|
+
transition:
|
|
32
|
+
background 150ms,
|
|
33
|
+
color 150ms;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/* When progress bar is shown, remove bottom radius */
|
|
@@ -55,8 +57,12 @@
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
@keyframes spin {
|
|
58
|
-
from {
|
|
59
|
-
|
|
60
|
+
from {
|
|
61
|
+
transform: rotate(0deg);
|
|
62
|
+
}
|
|
63
|
+
to {
|
|
64
|
+
transform: rotate(360deg);
|
|
65
|
+
}
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
/* Dropdown trigger */
|
|
@@ -190,13 +196,12 @@
|
|
|
190
196
|
.refreshButton span:not(.icon) {
|
|
191
197
|
display: none;
|
|
192
198
|
}
|
|
193
|
-
|
|
199
|
+
|
|
194
200
|
.refreshButton {
|
|
195
201
|
padding: var(--space-2);
|
|
196
202
|
}
|
|
197
|
-
|
|
203
|
+
|
|
198
204
|
.dropdownTrigger {
|
|
199
205
|
min-width: 50px;
|
|
200
206
|
}
|
|
201
207
|
}
|
|
202
|
-
|
|
@@ -28,14 +28,14 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
28
28
|
const [isChecking, setIsChecking] = useState(false);
|
|
29
29
|
const [selectedInterval, setSelectedInterval] = useState<IntervalOption>(INTERVAL_OPTIONS[0]);
|
|
30
30
|
const [progress, setProgress] = useState(0);
|
|
31
|
-
const [
|
|
32
|
-
|
|
31
|
+
const [_timeLeft, setTimeLeft] = useState<number | null>(null);
|
|
32
|
+
|
|
33
33
|
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
34
34
|
const progressRef = useRef<NodeJS.Timeout | null>(null);
|
|
35
35
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
36
36
|
const onRefreshRef = useRef(onRefresh);
|
|
37
37
|
const onForceCheckRef = useRef(onForceCheck);
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// Keep refs updated
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
onRefreshRef.current = onRefresh;
|
|
@@ -45,10 +45,10 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
45
45
|
// Handle manual refresh
|
|
46
46
|
const handleRefresh = useCallback(async () => {
|
|
47
47
|
if (isRefreshing || disabled) return;
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
setIsRefreshing(true);
|
|
50
50
|
setProgress(0);
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
try {
|
|
53
53
|
await onRefreshRef.current();
|
|
54
54
|
} finally {
|
|
@@ -72,10 +72,10 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
72
72
|
// Handle force check (runs actual health checks)
|
|
73
73
|
const handleForceCheck = useCallback(async () => {
|
|
74
74
|
if (isChecking || disabled || !onForceCheckRef.current) return;
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
setIsChecking(true);
|
|
77
77
|
setIsOpen(false);
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
try {
|
|
80
80
|
await onForceCheckRef.current();
|
|
81
81
|
} finally {
|
|
@@ -154,9 +154,7 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
154
154
|
disabled={isRefreshing || disabled}
|
|
155
155
|
title="Refresh now"
|
|
156
156
|
>
|
|
157
|
-
<span className={`${styles.icon} ${isRefreshing ? styles.spinning : ""}`}>
|
|
158
|
-
↻
|
|
159
|
-
</span>
|
|
157
|
+
<span className={`${styles.icon} ${isRefreshing ? styles.spinning : ""}`}>↻</span>
|
|
160
158
|
{isRefreshing ? "Refreshing..." : "Refresh"}
|
|
161
159
|
</button>
|
|
162
160
|
|
|
@@ -167,9 +165,7 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
167
165
|
disabled={disabled}
|
|
168
166
|
title="Set auto-refresh interval"
|
|
169
167
|
>
|
|
170
|
-
<span className={styles.intervalLabel}>
|
|
171
|
-
{selectedInterval.label}
|
|
172
|
-
</span>
|
|
168
|
+
<span className={styles.intervalLabel}>{selectedInterval.label}</span>
|
|
173
169
|
<span className={styles.chevron}>{isOpen ? "▲" : "▼"}</span>
|
|
174
170
|
</button>
|
|
175
171
|
</div>
|
|
@@ -177,10 +173,7 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
177
173
|
{/* Progress bar - only shows when auto-refresh is active */}
|
|
178
174
|
{isAutoRefresh && (
|
|
179
175
|
<div className={styles.progressBar}>
|
|
180
|
-
<div
|
|
181
|
-
className={styles.progressFill}
|
|
182
|
-
style={{ width: `${progress}%` }}
|
|
183
|
-
/>
|
|
176
|
+
<div className={styles.progressFill} style={{ width: `${progress}%` }} />
|
|
184
177
|
</div>
|
|
185
178
|
)}
|
|
186
179
|
</div>
|
|
@@ -219,4 +212,3 @@ export function RefreshButton({ onRefresh, onForceCheck, disabled }: RefreshButt
|
|
|
219
212
|
</div>
|
|
220
213
|
);
|
|
221
214
|
}
|
|
222
|
-
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
border-bottom: 2px solid transparent;
|
|
21
21
|
margin-bottom: -1px;
|
|
22
22
|
cursor: pointer;
|
|
23
|
-
transition:
|
|
23
|
+
transition:
|
|
24
|
+
color 150ms ease,
|
|
25
|
+
border-color 150ms ease;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
.trigger:hover:not(:disabled) {
|
|
@@ -55,4 +57,3 @@
|
|
|
55
57
|
.content[data-state="inactive"] {
|
|
56
58
|
display: none;
|
|
57
59
|
}
|
|
58
|
-
|
|
@@ -30,9 +30,7 @@ export const Default: Story = {
|
|
|
30
30
|
content: (
|
|
31
31
|
<Card>
|
|
32
32
|
<h3>Account Settings</h3>
|
|
33
|
-
<p style={{ marginTop: "8px" }}>
|
|
34
|
-
Manage your account settings and preferences.
|
|
35
|
-
</p>
|
|
33
|
+
<p style={{ marginTop: "8px" }}>Manage your account settings and preferences.</p>
|
|
36
34
|
</Card>
|
|
37
35
|
),
|
|
38
36
|
},
|
|
@@ -42,9 +40,7 @@ export const Default: Story = {
|
|
|
42
40
|
content: (
|
|
43
41
|
<Card>
|
|
44
42
|
<h3>Password Settings</h3>
|
|
45
|
-
<p style={{ marginTop: "8px" }}>
|
|
46
|
-
Update your password and security options.
|
|
47
|
-
</p>
|
|
43
|
+
<p style={{ marginTop: "8px" }}>Update your password and security options.</p>
|
|
48
44
|
</Card>
|
|
49
45
|
),
|
|
50
46
|
},
|
|
@@ -54,9 +50,7 @@ export const Default: Story = {
|
|
|
54
50
|
content: (
|
|
55
51
|
<Card>
|
|
56
52
|
<h3>Notification Preferences</h3>
|
|
57
|
-
<p style={{ marginTop: "8px" }}>
|
|
58
|
-
Configure how you receive notifications.
|
|
59
|
-
</p>
|
|
53
|
+
<p style={{ marginTop: "8px" }}>Configure how you receive notifications.</p>
|
|
60
54
|
</Card>
|
|
61
55
|
),
|
|
62
56
|
},
|
|
@@ -75,7 +69,7 @@ export const WithDisabledTab: Story = {
|
|
|
75
69
|
{
|
|
76
70
|
id: "disabled",
|
|
77
71
|
label: "Disabled",
|
|
78
|
-
content: <Card>You shouldn
|
|
72
|
+
content: <Card>You shouldn't see this.</Card>,
|
|
79
73
|
disabled: true,
|
|
80
74
|
},
|
|
81
75
|
{
|
|
@@ -98,4 +92,3 @@ export const ManyTabs: Story = {
|
|
|
98
92
|
],
|
|
99
93
|
},
|
|
100
94
|
};
|
|
101
|
-
|
|
@@ -45,11 +45,7 @@ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
|
|
45
45
|
))}
|
|
46
46
|
</TabsPrimitive.List>
|
|
47
47
|
{tabs.map((tab) => (
|
|
48
|
-
<TabsPrimitive.Content
|
|
49
|
-
key={tab.id}
|
|
50
|
-
value={tab.id}
|
|
51
|
-
className={styles.content}
|
|
52
|
-
>
|
|
48
|
+
<TabsPrimitive.Content key={tab.id} value={tab.id} className={styles.content}>
|
|
53
49
|
{tab.content}
|
|
54
50
|
</TabsPrimitive.Content>
|
|
55
51
|
))}
|
|
@@ -59,4 +55,3 @@ export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
|
|
59
55
|
);
|
|
60
56
|
|
|
61
57
|
Tabs.displayName = "Tabs";
|
|
62
|
-
|
|
@@ -97,7 +97,9 @@
|
|
|
97
97
|
background: transparent;
|
|
98
98
|
border: none;
|
|
99
99
|
cursor: pointer;
|
|
100
|
-
transition:
|
|
100
|
+
transition:
|
|
101
|
+
background-color 150ms ease,
|
|
102
|
+
color 150ms ease;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
.close:hover {
|
|
@@ -142,4 +144,3 @@
|
|
|
142
144
|
transform: translateX(100%);
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
|
-
|
|
@@ -98,9 +98,7 @@ const AllVariantsDemo = () => {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const closeToast = (id: number) => {
|
|
101
|
-
setToasts((prev) =>
|
|
102
|
-
prev.map((t) => (t.id === id ? { ...t, open: false } : t))
|
|
103
|
-
);
|
|
101
|
+
setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, open: false } : t)));
|
|
104
102
|
};
|
|
105
103
|
|
|
106
104
|
const titles = {
|
|
@@ -140,4 +138,3 @@ const AllVariantsDemo = () => {
|
|
|
140
138
|
export const AllVariants: Story = {
|
|
141
139
|
render: () => <AllVariantsDemo />,
|
|
142
140
|
};
|
|
143
|
-
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
forwardRef,
|
|
5
|
-
type ReactNode,
|
|
6
|
-
createContext,
|
|
7
|
-
useContext,
|
|
8
|
-
useState,
|
|
9
|
-
useCallback,
|
|
10
|
-
} from "react";
|
|
3
|
+
import { forwardRef, type ReactNode, createContext, useContext } from "react";
|
|
11
4
|
import * as ToastPrimitive from "@radix-ui/react-toast";
|
|
12
5
|
import { clsx } from "clsx";
|
|
13
6
|
import styles from "./Toast.module.css";
|
|
@@ -39,15 +32,7 @@ export interface ToastProps {
|
|
|
39
32
|
|
|
40
33
|
export const Toast = forwardRef<HTMLLIElement, ToastProps>(
|
|
41
34
|
(
|
|
42
|
-
{
|
|
43
|
-
open,
|
|
44
|
-
onOpenChange,
|
|
45
|
-
title,
|
|
46
|
-
description,
|
|
47
|
-
action,
|
|
48
|
-
variant = "default",
|
|
49
|
-
duration = 5000,
|
|
50
|
-
},
|
|
35
|
+
{ open, onOpenChange, title, description, action, variant = "default", duration = 5000 },
|
|
51
36
|
ref
|
|
52
37
|
) => {
|
|
53
38
|
return (
|
|
@@ -59,11 +44,7 @@ export const Toast = forwardRef<HTMLLIElement, ToastProps>(
|
|
|
59
44
|
className={clsx(styles.root, styles[variant])}
|
|
60
45
|
>
|
|
61
46
|
<div className={styles.content}>
|
|
62
|
-
{title &&
|
|
63
|
-
<ToastPrimitive.Title className={styles.title}>
|
|
64
|
-
{title}
|
|
65
|
-
</ToastPrimitive.Title>
|
|
66
|
-
)}
|
|
47
|
+
{title && <ToastPrimitive.Title className={styles.title}>{title}</ToastPrimitive.Title>}
|
|
67
48
|
{description && (
|
|
68
49
|
<ToastPrimitive.Description className={styles.description}>
|
|
69
50
|
{description}
|
|
@@ -120,4 +101,3 @@ export function useToast() {
|
|
|
120
101
|
}
|
|
121
102
|
return context;
|
|
122
103
|
}
|
|
123
|
-
|
|
@@ -15,17 +15,10 @@ export {
|
|
|
15
15
|
// Feedback Components
|
|
16
16
|
export { Badge, type BadgeProps } from "./Badge";
|
|
17
17
|
export { Modal, type ModalProps } from "./Modal";
|
|
18
|
-
export {
|
|
19
|
-
Toast,
|
|
20
|
-
ToastProvider,
|
|
21
|
-
useToast,
|
|
22
|
-
type ToastProps,
|
|
23
|
-
type ToastProviderProps,
|
|
24
|
-
} from "./Toast";
|
|
18
|
+
export { Toast, ToastProvider, useToast, type ToastProps, type ToastProviderProps } from "./Toast";
|
|
25
19
|
|
|
26
20
|
// Navigation Components
|
|
27
21
|
export { Tabs, type TabsProps, type TabItem } from "./Tabs";
|
|
28
22
|
|
|
29
23
|
// Action Components
|
|
30
24
|
export { RefreshButton } from "./RefreshButton";
|
|
31
|
-
|
package/lib/index.ts
CHANGED
package/lib/utils.ts
CHANGED
|
@@ -8,25 +8,24 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export function formatRelativeTime(date: Date | string | null): string {
|
|
10
10
|
if (!date) return "Never";
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
const now = new Date();
|
|
13
13
|
const then = typeof date === "string" ? new Date(date) : date;
|
|
14
14
|
const diffMs = now.getTime() - then.getTime();
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
const seconds = Math.floor(diffMs / 1000);
|
|
17
17
|
const minutes = Math.floor(seconds / 60);
|
|
18
18
|
const hours = Math.floor(minutes / 60);
|
|
19
19
|
const days = Math.floor(hours / 24);
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
if (seconds < 10) return "Just now";
|
|
22
22
|
if (seconds < 60) return `${seconds}s ago`;
|
|
23
23
|
if (minutes < 60) return `${minutes}m ago`;
|
|
24
24
|
if (hours < 24) return `${hours}h ago`;
|
|
25
25
|
if (days < 7) return `${days}d ago`;
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
return then.toLocaleDateString();
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// Re-export clsx for convenience
|
|
31
31
|
export { clsx } from "clsx";
|
|
32
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielthurau/atlas-labs-codex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Personal design system with React components, CSS tokens, and utilities",
|
|
5
5
|
"author": "thurau",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"import": "./lib/index.ts",
|
|
32
32
|
"types": "./lib/index.ts"
|
|
33
33
|
},
|
|
34
|
+
"./themes/css/reset.css": "./themes/css/reset.css",
|
|
34
35
|
"./themes/css/base.css": "./themes/css/base.css",
|
|
35
36
|
"./themes/css/theme-light.css": "./themes/css/theme-light.css",
|
|
36
37
|
"./themes/css/theme-dark.css": "./themes/css/theme-dark.css"
|
|
@@ -52,9 +53,13 @@
|
|
|
52
53
|
"build": "next build",
|
|
53
54
|
"start": "next start",
|
|
54
55
|
"lint": "next lint",
|
|
56
|
+
"lint:fix": "next lint --fix",
|
|
57
|
+
"format": "prettier --write .",
|
|
58
|
+
"format:check": "prettier --check .",
|
|
59
|
+
"check": "npm run lint && npm run format:check",
|
|
55
60
|
"storybook": "storybook dev -p 6006",
|
|
56
61
|
"build-storybook": "storybook build",
|
|
57
|
-
"prepublishOnly": "echo 'Ready to publish!'"
|
|
62
|
+
"prepublishOnly": "npm run check && echo 'Ready to publish!'"
|
|
58
63
|
},
|
|
59
64
|
"peerDependencies": {
|
|
60
65
|
"react": "^18.0.0",
|
|
@@ -89,6 +94,7 @@
|
|
|
89
94
|
"react": "^18.3.1",
|
|
90
95
|
"react-dom": "^18.3.1",
|
|
91
96
|
"storybook": "^8.4.7",
|
|
97
|
+
"prettier": "^3.4.2",
|
|
92
98
|
"typescript": "^5.7.2",
|
|
93
99
|
"vite": "^5.4.0"
|
|
94
100
|
}
|
package/themes/css/base.css
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/* Font Faces */
|
|
4
4
|
@font-face {
|
|
5
|
-
font-family:
|
|
6
|
-
src: url(
|
|
5
|
+
font-family: "Martian Mono";
|
|
6
|
+
src: url("/fonts/MartianMono-VariableFont_wdth,wght.ttf") format("truetype");
|
|
7
7
|
font-weight: 100 800;
|
|
8
8
|
font-stretch: 75% 112.5%;
|
|
9
9
|
font-display: swap;
|
|
@@ -55,10 +55,14 @@
|
|
|
55
55
|
--space-2: 0.5rem;
|
|
56
56
|
--space-3: 0.75rem;
|
|
57
57
|
--space-4: 1rem;
|
|
58
|
+
--space-5: 1.25rem;
|
|
58
59
|
--space-6: 1.5rem;
|
|
59
60
|
--space-8: 2rem;
|
|
61
|
+
--space-10: 2.5rem;
|
|
60
62
|
--space-12: 3rem;
|
|
61
63
|
--space-16: 4rem;
|
|
64
|
+
--space-20: 5rem;
|
|
65
|
+
--space-24: 6rem;
|
|
62
66
|
|
|
63
67
|
/* Semantic Spacing */
|
|
64
68
|
--spacing-inline: var(--space-2);
|
|
@@ -82,6 +86,8 @@
|
|
|
82
86
|
--text-lg: 1.125rem;
|
|
83
87
|
--text-xl: 1.5rem;
|
|
84
88
|
--text-2xl: 2rem;
|
|
89
|
+
--text-3xl: 2.5rem;
|
|
90
|
+
--text-4xl: 3rem;
|
|
85
91
|
|
|
86
92
|
/* Typography - Line Height */
|
|
87
93
|
--leading-xs: 1rem;
|
|
@@ -103,8 +109,8 @@
|
|
|
103
109
|
--tracking-wide: 0.05em;
|
|
104
110
|
|
|
105
111
|
/* Typography - Font Family */
|
|
106
|
-
--font-primary:
|
|
107
|
-
--font-sans:
|
|
112
|
+
--font-primary: "Martian Mono", "JetBrains Mono", "Fira Code", Consolas, monospace;
|
|
113
|
+
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
108
114
|
--font-mono: var(--font-primary);
|
|
109
115
|
|
|
110
116
|
/* Shadows */
|
|
@@ -139,4 +145,3 @@
|
|
|
139
145
|
--badge-radius: var(--radius-full);
|
|
140
146
|
--badge-font-size: var(--text-xs);
|
|
141
147
|
}
|
|
142
|
-
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* Design Codex - CSS Reset & Base Styles */
|
|
2
|
+
|
|
3
|
+
/* Box Sizing Reset */
|
|
4
|
+
*,
|
|
5
|
+
*::before,
|
|
6
|
+
*::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* Margin/Padding Reset */
|
|
11
|
+
* {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Base HTML */
|
|
17
|
+
html {
|
|
18
|
+
-webkit-font-smoothing: antialiased;
|
|
19
|
+
-moz-osx-font-smoothing: grayscale;
|
|
20
|
+
text-size-adjust: 100%;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Body Defaults */
|
|
24
|
+
body {
|
|
25
|
+
font-family: var(--font-primary);
|
|
26
|
+
font-size: var(--text-base);
|
|
27
|
+
line-height: 1.5;
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
background-color: var(--color-bg-default);
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Typography - Headings */
|
|
34
|
+
h1,
|
|
35
|
+
h2,
|
|
36
|
+
h3,
|
|
37
|
+
h4,
|
|
38
|
+
h5,
|
|
39
|
+
h6 {
|
|
40
|
+
font-weight: var(--font-semibold);
|
|
41
|
+
color: var(--color-text-primary);
|
|
42
|
+
line-height: 1.25;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
h1 {
|
|
46
|
+
font-size: var(--text-2xl);
|
|
47
|
+
}
|
|
48
|
+
h2 {
|
|
49
|
+
font-size: var(--text-xl);
|
|
50
|
+
}
|
|
51
|
+
h3 {
|
|
52
|
+
font-size: var(--text-lg);
|
|
53
|
+
}
|
|
54
|
+
h4 {
|
|
55
|
+
font-size: var(--text-base);
|
|
56
|
+
}
|
|
57
|
+
h5 {
|
|
58
|
+
font-size: var(--text-sm);
|
|
59
|
+
}
|
|
60
|
+
h6 {
|
|
61
|
+
font-size: var(--text-xs);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Typography - Paragraphs */
|
|
65
|
+
p {
|
|
66
|
+
color: var(--color-text-secondary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Links */
|
|
70
|
+
a {
|
|
71
|
+
color: inherit;
|
|
72
|
+
text-decoration: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
a:hover {
|
|
76
|
+
text-decoration: underline;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Buttons */
|
|
80
|
+
button {
|
|
81
|
+
font: inherit;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
border: none;
|
|
84
|
+
background: none;
|
|
85
|
+
color: inherit;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Form Elements */
|
|
89
|
+
input,
|
|
90
|
+
textarea,
|
|
91
|
+
select {
|
|
92
|
+
font: inherit;
|
|
93
|
+
color: inherit;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Images */
|
|
97
|
+
img,
|
|
98
|
+
picture,
|
|
99
|
+
video,
|
|
100
|
+
canvas,
|
|
101
|
+
svg {
|
|
102
|
+
display: block;
|
|
103
|
+
max-width: 100%;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Lists */
|
|
107
|
+
ul,
|
|
108
|
+
ol {
|
|
109
|
+
list-style: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Tables */
|
|
113
|
+
table {
|
|
114
|
+
border-collapse: collapse;
|
|
115
|
+
border-spacing: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Focus States */
|
|
119
|
+
:focus-visible {
|
|
120
|
+
outline: none;
|
|
121
|
+
box-shadow: var(--focus-ring);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Selection */
|
|
125
|
+
::selection {
|
|
126
|
+
background-color: var(--color-intent-primary);
|
|
127
|
+
color: white;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Reduced Motion */
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
*,
|
|
133
|
+
*::before,
|
|
134
|
+
*::after {
|
|
135
|
+
animation-duration: 0.01ms !important;
|
|
136
|
+
animation-iteration-count: 1 !important;
|
|
137
|
+
transition-duration: 0.01ms !important;
|
|
138
|
+
scroll-behavior: auto !important;
|
|
139
|
+
}
|
|
140
|
+
}
|