@codelitdev/design-system 0.1.0-alpha.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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +68 -0
- package/package.json +44 -0
- package/src/assets/logo-codelit.svg +1 -0
- package/src/assets/logo-courselit.svg +1 -0
- package/src/assets/logo-frontlit.svg +1 -0
- package/src/assets/logo-medialit.svg +1 -0
- package/src/assets/logo-sendlit.svg +1 -0
- package/src/components/actions/Button.prompt.md +8 -0
- package/src/components/actions/Button.tsx +27 -0
- package/src/components/actions/IconButton.prompt.md +7 -0
- package/src/components/actions/IconButton.tsx +28 -0
- package/src/components/display/Badge.prompt.md +8 -0
- package/src/components/display/Badge.tsx +19 -0
- package/src/components/display/Card.prompt.md +9 -0
- package/src/components/display/Card.tsx +26 -0
- package/src/components/display/Tabs.prompt.md +5 -0
- package/src/components/display/Tabs.tsx +31 -0
- package/src/components/feedback/Dialog.prompt.md +6 -0
- package/src/components/feedback/Dialog.tsx +35 -0
- package/src/components/feedback/Toast.prompt.md +5 -0
- package/src/components/feedback/Toast.tsx +27 -0
- package/src/components/feedback/Tooltip.prompt.md +7 -0
- package/src/components/feedback/Tooltip.tsx +26 -0
- package/src/components/forms/Checkbox.prompt.md +5 -0
- package/src/components/forms/Checkbox.tsx +30 -0
- package/src/components/forms/Input.prompt.md +8 -0
- package/src/components/forms/Input.tsx +40 -0
- package/src/components/forms/Radio.prompt.md +6 -0
- package/src/components/forms/Radio.tsx +31 -0
- package/src/components/forms/Select.prompt.md +7 -0
- package/src/components/forms/Select.tsx +41 -0
- package/src/components/forms/Switch.prompt.md +5 -0
- package/src/components/forms/Switch.tsx +23 -0
- package/src/components.css +182 -0
- package/src/index.ts +13 -0
- package/src/styles.css +6 -0
- package/src/tokens/colors.css +83 -0
- package/src/tokens/fonts.css +2 -0
- package/src/tokens/layout.css +37 -0
- package/src/tokens/products.css +63 -0
- package/src/tokens/typography.css +35 -0
- package/tailwind-preset.js +43 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface CheckboxProps {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
/** Muted second line under the label. */
|
|
6
|
+
description?: React.ReactNode;
|
|
7
|
+
checked?: boolean;
|
|
8
|
+
defaultChecked?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
onChange?: (e: React.ChangeEvent) => void;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Checkbox with label and optional sub-description. */
|
|
15
|
+
export function Checkbox({ label, description, checked, defaultChecked, disabled, onChange, style }: CheckboxProps) {
|
|
16
|
+
return (
|
|
17
|
+
<label className={`cl-check ${disabled ? "cl-check--disabled" : ""}`} style={style}>
|
|
18
|
+
<input type="checkbox" checked={checked} defaultChecked={defaultChecked} disabled={disabled} onChange={onChange} />
|
|
19
|
+
<span className="cl-check__box">
|
|
20
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5"></path></svg>
|
|
21
|
+
</span>
|
|
22
|
+
{label ? (
|
|
23
|
+
<span className="cl-check__label">
|
|
24
|
+
{label}
|
|
25
|
+
{description ? <span className="cl-check__sub">{description}</span> : null}
|
|
26
|
+
</span>
|
|
27
|
+
) : null}
|
|
28
|
+
</label>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Text field with label/hint/error built in; `multiline` turns it into a textarea.
|
|
2
|
+
|
|
3
|
+
```jsx
|
|
4
|
+
<Input label="Course title" placeholder="e.g. Intro to TypeScript" hint="Shown on your public page." />
|
|
5
|
+
<Input label="From address" error="That doesn't look like an email." />
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Focus shows an accent-wash ring; errors switch to destructive.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface InputProps {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
/** Helper text below the field; replaced by `error` when set. */
|
|
6
|
+
hint?: React.ReactNode;
|
|
7
|
+
/** Error message; also switches the border to destructive. */
|
|
8
|
+
error?: React.ReactNode;
|
|
9
|
+
/** Render a textarea instead. */
|
|
10
|
+
multiline?: boolean;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
type?: string;
|
|
16
|
+
onChange?: (e: React.ChangeEvent) => void;
|
|
17
|
+
style?: React.CSSProperties;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Text input (or textarea via multiline) with optional label, hint, and error. */
|
|
21
|
+
export function Input({ label, hint, error, multiline, style, ...rest }: InputProps) {
|
|
22
|
+
const cls = `cl-input ${error ? "cl-input--invalid" : ""}`;
|
|
23
|
+
const control = multiline ? (
|
|
24
|
+
<textarea className={cls} {...rest} />
|
|
25
|
+
) : (
|
|
26
|
+
<input className={cls} {...rest} />
|
|
27
|
+
);
|
|
28
|
+
if (!label && !hint && !error) return control;
|
|
29
|
+
return (
|
|
30
|
+
<label className="cl-field" style={style}>
|
|
31
|
+
{label ? <span className="cl-field__label">{label}</span> : null}
|
|
32
|
+
{control}
|
|
33
|
+
{error ? (
|
|
34
|
+
<span className="cl-field__error">{error}</span>
|
|
35
|
+
) : hint ? (
|
|
36
|
+
<span className="cl-field__hint">{hint}</span>
|
|
37
|
+
) : null}
|
|
38
|
+
</label>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface RadioProps {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
description?: React.ReactNode;
|
|
6
|
+
name?: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
defaultChecked?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
onChange?: (e: React.ChangeEvent) => void;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Radio button; group by sharing `name`. */
|
|
16
|
+
export function Radio({ label, description, name, value, checked, defaultChecked, disabled, onChange, style }: RadioProps) {
|
|
17
|
+
return (
|
|
18
|
+
<label className={`cl-check ${disabled ? "cl-check--disabled" : ""}`} style={style}>
|
|
19
|
+
<input type="radio" name={name} value={value} checked={checked} defaultChecked={defaultChecked} disabled={disabled} onChange={onChange} />
|
|
20
|
+
<span className="cl-check__box cl-check__box--radio">
|
|
21
|
+
<span className="cl-check__radio-dot"></span>
|
|
22
|
+
</span>
|
|
23
|
+
{label ? (
|
|
24
|
+
<span className="cl-check__label">
|
|
25
|
+
{label}
|
|
26
|
+
{description ? <span className="cl-check__sub">{description}</span> : null}
|
|
27
|
+
</span>
|
|
28
|
+
) : null}
|
|
29
|
+
</label>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface SelectProps {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
hint?: React.ReactNode;
|
|
6
|
+
/** Strings or {value, label} pairs. */
|
|
7
|
+
options: Array<string | { value: string; label: string }>;
|
|
8
|
+
value?: string;
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
onChange?: (e: React.ChangeEvent) => void;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Native select styled to match Input, with chevron. */
|
|
16
|
+
export function Select({ label, hint, options = [], style, ...rest }: SelectProps) {
|
|
17
|
+
const control = (
|
|
18
|
+
<span className="cl-select">
|
|
19
|
+
<select {...rest}>
|
|
20
|
+
{options.map((o) =>
|
|
21
|
+
typeof o === "string" ? (
|
|
22
|
+
<option key={o} value={o}>{o}</option>
|
|
23
|
+
) : (
|
|
24
|
+
<option key={o.value} value={o.value}>{o.label}</option>
|
|
25
|
+
)
|
|
26
|
+
)}
|
|
27
|
+
</select>
|
|
28
|
+
<span className="cl-select__chevron">
|
|
29
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"></path></svg>
|
|
30
|
+
</span>
|
|
31
|
+
</span>
|
|
32
|
+
);
|
|
33
|
+
if (!label && !hint) return <span style={style}>{control}</span>;
|
|
34
|
+
return (
|
|
35
|
+
<label className="cl-field" style={style}>
|
|
36
|
+
{label ? <span className="cl-field__label">{label}</span> : null}
|
|
37
|
+
{control}
|
|
38
|
+
{hint ? <span className="cl-field__hint">{hint}</span> : null}
|
|
39
|
+
</label>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface SwitchProps {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
checked?: boolean;
|
|
6
|
+
defaultChecked?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange?: (e: React.ChangeEvent) => void;
|
|
9
|
+
style?: React.CSSProperties;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Toggle switch with trailing label. */
|
|
13
|
+
export function Switch({ label, checked, defaultChecked, disabled, onChange, style }: SwitchProps) {
|
|
14
|
+
return (
|
|
15
|
+
<label className="cl-switch" style={{ opacity: disabled ? 0.5 : 1, ...style }}>
|
|
16
|
+
<input type="checkbox" role="switch" checked={checked} defaultChecked={defaultChecked} disabled={disabled} onChange={onChange} />
|
|
17
|
+
<span className="cl-switch__track">
|
|
18
|
+
<span className="cl-switch__thumb"></span>
|
|
19
|
+
</span>
|
|
20
|
+
{label ? <span>{label}</span> : null}
|
|
21
|
+
</label>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/* CodeLit — component class styles (backing the React primitives in components/core). */
|
|
2
|
+
|
|
3
|
+
/* ---------- Button ---------- */
|
|
4
|
+
.cl-btn {
|
|
5
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
6
|
+
font: 600 0.875rem/1 var(--font-sans);
|
|
7
|
+
border-radius: var(--radius); border: 1px solid transparent;
|
|
8
|
+
cursor: pointer; white-space: nowrap;
|
|
9
|
+
transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
|
|
10
|
+
}
|
|
11
|
+
.cl-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
12
|
+
.cl-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
13
|
+
.cl-btn--md { height: 36px; padding: 0 16px; }
|
|
14
|
+
.cl-btn--sm { height: 30px; padding: 0 12px; font-size: 0.8125rem; }
|
|
15
|
+
.cl-btn--lg { height: 42px; padding: 0 20px; font-size: 0.9375rem; }
|
|
16
|
+
.cl-btn--primary { background: var(--primary); color: var(--primary-foreground); }
|
|
17
|
+
.cl-btn--primary:hover:not(:disabled) { background: var(--primary-hover); }
|
|
18
|
+
.cl-btn--secondary { background: var(--secondary); color: var(--secondary-foreground); }
|
|
19
|
+
.cl-btn--secondary:hover:not(:disabled) { background: var(--border); }
|
|
20
|
+
.cl-btn--outline { background: transparent; color: var(--foreground); border-color: var(--input); }
|
|
21
|
+
.cl-btn--outline:hover:not(:disabled) { background: var(--muted); }
|
|
22
|
+
.cl-btn--ghost { background: transparent; color: var(--foreground); }
|
|
23
|
+
.cl-btn--ghost:hover:not(:disabled) { background: var(--muted); }
|
|
24
|
+
.cl-btn--destructive { background: var(--destructive); color: var(--destructive-foreground); }
|
|
25
|
+
.cl-btn--destructive:hover:not(:disabled) { filter: brightness(0.92); }
|
|
26
|
+
.cl-btn--soft { background: var(--primary-soft); color: var(--primary); }
|
|
27
|
+
.cl-btn--soft:hover:not(:disabled) { background: color-mix(in oklch, var(--primary-soft), var(--primary) 10%); }
|
|
28
|
+
|
|
29
|
+
/* ---------- IconButton ---------- */
|
|
30
|
+
.cl-iconbtn {
|
|
31
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
32
|
+
border-radius: var(--radius); border: 1px solid transparent;
|
|
33
|
+
background: transparent; color: var(--muted-foreground); cursor: pointer;
|
|
34
|
+
transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
|
|
35
|
+
}
|
|
36
|
+
.cl-iconbtn--md { width: 34px; height: 34px; }
|
|
37
|
+
.cl-iconbtn--sm { width: 28px; height: 28px; }
|
|
38
|
+
.cl-iconbtn:hover { background: var(--muted); color: var(--foreground); }
|
|
39
|
+
.cl-iconbtn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
40
|
+
.cl-iconbtn--outline { border-color: var(--input); }
|
|
41
|
+
|
|
42
|
+
/* ---------- Badge ---------- */
|
|
43
|
+
.cl-badge {
|
|
44
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
45
|
+
font: 600 0.75rem/1 var(--font-sans);
|
|
46
|
+
padding: 4px 10px; border-radius: var(--radius-full);
|
|
47
|
+
}
|
|
48
|
+
.cl-badge--default { background: var(--primary-soft); color: var(--primary); }
|
|
49
|
+
.cl-badge--neutral { background: var(--muted); color: var(--muted-foreground); }
|
|
50
|
+
.cl-badge--success { background: var(--success-soft); color: var(--success); }
|
|
51
|
+
.cl-badge--warning { background: var(--warning-soft); color: var(--warning); }
|
|
52
|
+
.cl-badge--destructive { background: var(--destructive-soft); color: var(--destructive); }
|
|
53
|
+
.cl-badge--outline { background: transparent; color: var(--muted-foreground); box-shadow: inset 0 0 0 1px var(--border); }
|
|
54
|
+
.cl-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
|
55
|
+
|
|
56
|
+
/* ---------- Card ---------- */
|
|
57
|
+
.cl-card {
|
|
58
|
+
background: var(--card); color: var(--card-foreground);
|
|
59
|
+
border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
60
|
+
box-shadow: var(--shadow-card);
|
|
61
|
+
}
|
|
62
|
+
.cl-card__header { display: flex; flex-direction: column; gap: 4px; padding: 20px 20px 0; }
|
|
63
|
+
.cl-card__title { font: 600 1rem/1.3 var(--font-sans); margin: 0; }
|
|
64
|
+
.cl-card__description { font: 400 0.875rem/1.5 var(--font-sans); color: var(--muted-foreground); margin: 0; }
|
|
65
|
+
.cl-card__content { padding: 20px; }
|
|
66
|
+
.cl-card__footer { display: flex; align-items: center; gap: 10px; padding: 0 20px 20px; }
|
|
67
|
+
|
|
68
|
+
/* ---------- Input / Textarea ---------- */
|
|
69
|
+
.cl-field { display: flex; flex-direction: column; gap: 6px; }
|
|
70
|
+
.cl-field__label { font: var(--text-label); color: var(--foreground); }
|
|
71
|
+
.cl-field__hint { font: var(--text-caption); color: var(--muted-foreground); }
|
|
72
|
+
.cl-field__error { font: var(--text-caption); color: var(--destructive); }
|
|
73
|
+
.cl-input {
|
|
74
|
+
height: 36px; padding: 0 12px; width: 100%; box-sizing: border-box;
|
|
75
|
+
font: 400 0.875rem/1 var(--font-sans); color: var(--foreground);
|
|
76
|
+
background: var(--card); border: 1px solid var(--input); border-radius: var(--radius);
|
|
77
|
+
transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
|
|
78
|
+
}
|
|
79
|
+
.cl-input::placeholder { color: var(--muted-foreground); }
|
|
80
|
+
.cl-input:focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px var(--primary-soft); }
|
|
81
|
+
.cl-input:disabled { opacity: 0.5; cursor: not-allowed; background: var(--muted); }
|
|
82
|
+
.cl-input--invalid { border-color: var(--destructive); }
|
|
83
|
+
.cl-input--invalid:focus { border-color: var(--destructive); box-shadow: 0 0 0 3px var(--destructive-soft); }
|
|
84
|
+
textarea.cl-input { height: auto; min-height: 80px; padding: 10px 12px; line-height: 1.5; resize: vertical; }
|
|
85
|
+
|
|
86
|
+
/* ---------- Select ---------- */
|
|
87
|
+
.cl-select { position: relative; display: inline-flex; width: 100%; }
|
|
88
|
+
.cl-select select {
|
|
89
|
+
appearance: none; width: 100%; height: 36px; padding: 0 32px 0 12px;
|
|
90
|
+
font: 400 0.875rem/1 var(--font-sans); color: var(--foreground);
|
|
91
|
+
background: var(--card); border: 1px solid var(--input); border-radius: var(--radius); cursor: pointer;
|
|
92
|
+
}
|
|
93
|
+
.cl-select select:focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px var(--primary-soft); }
|
|
94
|
+
.cl-select__chevron { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; color: var(--muted-foreground); display: flex; }
|
|
95
|
+
|
|
96
|
+
/* ---------- Checkbox / Radio ---------- */
|
|
97
|
+
.cl-check { display: inline-flex; align-items: flex-start; gap: 10px; cursor: pointer; font: var(--text-body-sm); }
|
|
98
|
+
.cl-check input { position: absolute; opacity: 0; width: 0; height: 0; }
|
|
99
|
+
.cl-check__box {
|
|
100
|
+
flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 5px;
|
|
101
|
+
border: 1.5px solid var(--input); background: var(--card);
|
|
102
|
+
display: grid; place-items: center; color: var(--primary-foreground);
|
|
103
|
+
transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
|
|
104
|
+
}
|
|
105
|
+
.cl-check__box--radio { border-radius: 50%; }
|
|
106
|
+
.cl-check input:checked + .cl-check__box { background: var(--primary); border-color: var(--primary); }
|
|
107
|
+
.cl-check input:focus-visible + .cl-check__box { box-shadow: var(--shadow-focus); }
|
|
108
|
+
.cl-check__radio-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--primary-foreground); opacity: 0; }
|
|
109
|
+
.cl-check input:checked + .cl-check__box .cl-check__radio-dot { opacity: 1; }
|
|
110
|
+
.cl-check__label { color: var(--foreground); }
|
|
111
|
+
.cl-check__sub { display: block; font: var(--text-caption); color: var(--muted-foreground); margin-top: 2px; }
|
|
112
|
+
.cl-check--disabled { opacity: 0.5; cursor: not-allowed; }
|
|
113
|
+
|
|
114
|
+
/* ---------- Switch ---------- */
|
|
115
|
+
.cl-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font: var(--text-body-sm); }
|
|
116
|
+
.cl-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
|
|
117
|
+
.cl-switch__track {
|
|
118
|
+
flex: none; width: 36px; height: 21px; border-radius: var(--radius-full);
|
|
119
|
+
background: var(--input); position: relative;
|
|
120
|
+
transition: background var(--duration-fast) var(--ease-out);
|
|
121
|
+
}
|
|
122
|
+
.cl-switch__thumb {
|
|
123
|
+
position: absolute; top: 2.5px; left: 2.5px; width: 16px; height: 16px; border-radius: 50%;
|
|
124
|
+
background: var(--card); box-shadow: 0 1px 3px oklch(0.3 0.02 60 / 0.3);
|
|
125
|
+
transition: transform var(--duration-fast) var(--ease-out);
|
|
126
|
+
}
|
|
127
|
+
.cl-switch input:checked + .cl-switch__track { background: var(--primary); }
|
|
128
|
+
.cl-switch input:checked + .cl-switch__track .cl-switch__thumb { transform: translateX(15px); }
|
|
129
|
+
.cl-switch input:focus-visible + .cl-switch__track { box-shadow: var(--shadow-focus); }
|
|
130
|
+
|
|
131
|
+
/* ---------- Tabs ---------- */
|
|
132
|
+
.cl-tabs__list {
|
|
133
|
+
display: inline-flex; gap: 2px; padding: 3px;
|
|
134
|
+
background: var(--muted); border-radius: var(--radius);
|
|
135
|
+
}
|
|
136
|
+
.cl-tabs__trigger {
|
|
137
|
+
font: 600 0.8125rem/1 var(--font-sans); color: var(--muted-foreground);
|
|
138
|
+
padding: 7px 14px; border-radius: 7px; border: none; background: transparent; cursor: pointer;
|
|
139
|
+
transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
|
|
140
|
+
}
|
|
141
|
+
.cl-tabs__trigger:hover { color: var(--foreground); }
|
|
142
|
+
.cl-tabs__trigger[data-active="true"] { background: var(--card); color: var(--foreground); box-shadow: var(--shadow-card); }
|
|
143
|
+
.cl-tabs__list--underline { background: transparent; padding: 0; gap: 4px; border-bottom: 1px solid var(--border); border-radius: 0; }
|
|
144
|
+
.cl-tabs__list--underline .cl-tabs__trigger { border-radius: 0; padding: 10px 12px; margin-bottom: -1px; border-bottom: 2px solid transparent; }
|
|
145
|
+
.cl-tabs__list--underline .cl-tabs__trigger[data-active="true"] { background: transparent; box-shadow: none; border-bottom-color: var(--primary); color: var(--foreground); }
|
|
146
|
+
|
|
147
|
+
/* ---------- Dialog ---------- */
|
|
148
|
+
.cl-dialog__scrim {
|
|
149
|
+
position: fixed; inset: 0; background: var(--scrim);
|
|
150
|
+
display: grid; place-items: center; z-index: 50; padding: 24px;
|
|
151
|
+
}
|
|
152
|
+
.cl-dialog {
|
|
153
|
+
background: var(--card); color: var(--card-foreground);
|
|
154
|
+
border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
155
|
+
box-shadow: var(--shadow-popover); width: 100%; max-width: 440px;
|
|
156
|
+
padding: 24px; box-sizing: border-box;
|
|
157
|
+
}
|
|
158
|
+
.cl-dialog__title { font: 600 1.125rem/1.3 var(--font-sans); margin: 0 0 6px; }
|
|
159
|
+
.cl-dialog__description { font: var(--text-body-sm); color: var(--muted-foreground); margin: 0 0 20px; }
|
|
160
|
+
.cl-dialog__footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
|
161
|
+
|
|
162
|
+
/* ---------- Toast ---------- */
|
|
163
|
+
.cl-toast {
|
|
164
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
165
|
+
background: var(--popover); color: var(--popover-foreground);
|
|
166
|
+
border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
167
|
+
box-shadow: var(--shadow-popover); padding: 14px 16px; width: 340px; box-sizing: border-box;
|
|
168
|
+
}
|
|
169
|
+
.cl-toast__icon { flex: none; display: flex; margin-top: 1px; color: var(--primary); }
|
|
170
|
+
.cl-toast--success .cl-toast__icon { color: var(--success); }
|
|
171
|
+
.cl-toast--destructive .cl-toast__icon { color: var(--destructive); }
|
|
172
|
+
.cl-toast__title { font: 600 0.875rem/1.3 var(--font-sans); margin: 0; }
|
|
173
|
+
.cl-toast__description { font: var(--text-caption); color: var(--muted-foreground); margin: 2px 0 0; }
|
|
174
|
+
|
|
175
|
+
/* ---------- Tooltip ---------- */
|
|
176
|
+
.cl-tooltip-wrap { position: relative; display: inline-flex; }
|
|
177
|
+
.cl-tooltip {
|
|
178
|
+
position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
|
|
179
|
+
background: var(--foreground); color: var(--background);
|
|
180
|
+
font: 500 0.75rem/1.3 var(--font-sans); padding: 6px 10px; border-radius: var(--radius-sm);
|
|
181
|
+
white-space: nowrap; pointer-events: none; z-index: 60;
|
|
182
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { Button } from "./components/actions/Button";
|
|
2
|
+
export { IconButton } from "./components/actions/IconButton";
|
|
3
|
+
export { Badge } from "./components/display/Badge";
|
|
4
|
+
export { Card } from "./components/display/Card";
|
|
5
|
+
export { Tabs } from "./components/display/Tabs";
|
|
6
|
+
export { Dialog } from "./components/feedback/Dialog";
|
|
7
|
+
export { Toast } from "./components/feedback/Toast";
|
|
8
|
+
export { Tooltip } from "./components/feedback/Tooltip";
|
|
9
|
+
export { Checkbox } from "./components/forms/Checkbox";
|
|
10
|
+
export { Input } from "./components/forms/Input";
|
|
11
|
+
export { Radio } from "./components/forms/Radio";
|
|
12
|
+
export { Select } from "./components/forms/Select";
|
|
13
|
+
export { Switch } from "./components/forms/Switch";
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* CodeLit — color tokens. shadcn/ui variable convention: :root = light, .dark = dark. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Warm neutral ramp (amber-toned, chroma <= 0.015) */
|
|
5
|
+
--neutral-50: oklch(0.985 0.004 85);
|
|
6
|
+
--neutral-100: oklch(0.955 0.008 82);
|
|
7
|
+
--neutral-200: oklch(0.92 0.01 80);
|
|
8
|
+
--neutral-300: oklch(0.87 0.012 80);
|
|
9
|
+
--neutral-400: oklch(0.7 0.015 75);
|
|
10
|
+
--neutral-500: oklch(0.55 0.018 70);
|
|
11
|
+
--neutral-600: oklch(0.45 0.018 68);
|
|
12
|
+
--neutral-700: oklch(0.35 0.016 64);
|
|
13
|
+
--neutral-800: oklch(0.27 0.014 62);
|
|
14
|
+
--neutral-900: oklch(0.22 0.014 60);
|
|
15
|
+
|
|
16
|
+
/* Brand + product accents — shared L/C, hue varies */
|
|
17
|
+
--brand-amber: oklch(0.66 0.14 60);
|
|
18
|
+
--accent-courselit: oklch(0.6 0.14 30);
|
|
19
|
+
--accent-medialit: oklch(0.6 0.14 300);
|
|
20
|
+
--accent-sendlit: oklch(0.6 0.14 150);
|
|
21
|
+
--accent-frontlit: oklch(0.6 0.14 230);
|
|
22
|
+
|
|
23
|
+
/* shadcn semantic — light */
|
|
24
|
+
--background: oklch(0.975 0.006 80);
|
|
25
|
+
--foreground: oklch(0.24 0.02 60);
|
|
26
|
+
--card: oklch(0.995 0.003 85);
|
|
27
|
+
--card-foreground: oklch(0.24 0.02 60);
|
|
28
|
+
--popover: oklch(0.995 0.003 85);
|
|
29
|
+
--popover-foreground: oklch(0.24 0.02 60);
|
|
30
|
+
--primary: var(--brand-amber);
|
|
31
|
+
--primary-foreground: oklch(0.99 0.005 85);
|
|
32
|
+
--primary-soft: oklch(0.66 0.14 60 / 0.13);
|
|
33
|
+
--primary-hover: oklch(0.61 0.14 60);
|
|
34
|
+
--secondary: oklch(0.94 0.009 80);
|
|
35
|
+
--secondary-foreground: oklch(0.3 0.018 62);
|
|
36
|
+
--muted: oklch(0.94 0.009 80);
|
|
37
|
+
--muted-foreground: oklch(0.5 0.018 70);
|
|
38
|
+
--accent: oklch(0.94 0.009 80);
|
|
39
|
+
--accent-foreground: oklch(0.3 0.018 62);
|
|
40
|
+
--destructive: oklch(0.55 0.17 27);
|
|
41
|
+
--destructive-foreground: oklch(0.99 0.005 85);
|
|
42
|
+
--destructive-soft: oklch(0.55 0.17 27 / 0.12);
|
|
43
|
+
--success: oklch(0.56 0.13 150);
|
|
44
|
+
--success-soft: oklch(0.56 0.13 150 / 0.12);
|
|
45
|
+
--warning: oklch(0.66 0.13 75);
|
|
46
|
+
--warning-soft: oklch(0.66 0.13 75 / 0.15);
|
|
47
|
+
--border: oklch(0.9 0.012 80);
|
|
48
|
+
--input: oklch(0.87 0.012 80);
|
|
49
|
+
--ring: var(--primary);
|
|
50
|
+
|
|
51
|
+
--scrim: oklch(0.2 0.02 60 / 0.4);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dark {
|
|
55
|
+
--background: oklch(0.2 0.014 60);
|
|
56
|
+
--foreground: oklch(0.93 0.008 80);
|
|
57
|
+
--card: oklch(0.24 0.015 60);
|
|
58
|
+
--card-foreground: oklch(0.93 0.008 80);
|
|
59
|
+
--popover: oklch(0.26 0.015 60);
|
|
60
|
+
--popover-foreground: oklch(0.93 0.008 80);
|
|
61
|
+
--primary: oklch(0.72 0.13 60);
|
|
62
|
+
--primary-foreground: oklch(0.2 0.04 60);
|
|
63
|
+
--primary-soft: oklch(0.72 0.13 60 / 0.16);
|
|
64
|
+
--primary-hover: oklch(0.77 0.13 60);
|
|
65
|
+
--secondary: oklch(0.29 0.015 62);
|
|
66
|
+
--secondary-foreground: oklch(0.88 0.01 80);
|
|
67
|
+
--muted: oklch(0.28 0.015 62);
|
|
68
|
+
--muted-foreground: oklch(0.66 0.015 75);
|
|
69
|
+
--accent: oklch(0.29 0.015 62);
|
|
70
|
+
--accent-foreground: oklch(0.88 0.01 80);
|
|
71
|
+
--destructive: oklch(0.62 0.17 27);
|
|
72
|
+
--destructive-foreground: oklch(0.99 0.005 85);
|
|
73
|
+
--destructive-soft: oklch(0.62 0.17 27 / 0.18);
|
|
74
|
+
--success: oklch(0.66 0.13 150);
|
|
75
|
+
--success-soft: oklch(0.66 0.13 150 / 0.16);
|
|
76
|
+
--warning: oklch(0.72 0.13 75);
|
|
77
|
+
--warning-soft: oklch(0.72 0.13 75 / 0.18);
|
|
78
|
+
--border: oklch(0.32 0.015 64);
|
|
79
|
+
--input: oklch(0.36 0.015 64);
|
|
80
|
+
--ring: var(--primary);
|
|
81
|
+
|
|
82
|
+
--scrim: oklch(0.1 0.01 60 / 0.55);
|
|
83
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* CodeLit — spacing, radii, shadows, motion. 4px grid. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Spacing */
|
|
5
|
+
--space-1: 4px;
|
|
6
|
+
--space-2: 8px;
|
|
7
|
+
--space-3: 12px;
|
|
8
|
+
--space-4: 16px;
|
|
9
|
+
--space-5: 20px;
|
|
10
|
+
--space-6: 24px;
|
|
11
|
+
--space-8: 32px;
|
|
12
|
+
--space-10: 40px;
|
|
13
|
+
--space-12: 48px;
|
|
14
|
+
|
|
15
|
+
/* Radii */
|
|
16
|
+
--radius: 0.625rem; /* 10px — controls, inputs, buttons */
|
|
17
|
+
--radius-sm: 6px; /* chips, code spans, small tiles */
|
|
18
|
+
--radius-lg: 16px; /* cards, dialogs */
|
|
19
|
+
--radius-full: 999px; /* pills, badges, avatars */
|
|
20
|
+
|
|
21
|
+
/* Shadows — warm-tinted, soft */
|
|
22
|
+
--shadow-card: 0 2px 12px oklch(0.3 0.02 60 / 0.06);
|
|
23
|
+
--shadow-raised: 0 4px 20px oklch(0.3 0.02 60 / 0.1);
|
|
24
|
+
--shadow-popover: 0 8px 32px oklch(0.3 0.02 60 / 0.14);
|
|
25
|
+
--shadow-focus: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
|
|
26
|
+
|
|
27
|
+
/* Motion */
|
|
28
|
+
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
29
|
+
--duration-fast: 150ms;
|
|
30
|
+
--duration-base: 200ms;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.dark {
|
|
34
|
+
--shadow-card: 0 2px 12px oklch(0 0 0 / 0.25);
|
|
35
|
+
--shadow-raised: 0 4px 20px oklch(0 0 0 / 0.35);
|
|
36
|
+
--shadow-popover: 0 8px 32px oklch(0 0 0 / 0.45);
|
|
37
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* CodeLit — per-product accent scopes.
|
|
2
|
+
Usage: <body data-product="courselit"> (optionally + class="dark").
|
|
3
|
+
No attribute = CodeLit amber. Only the accent family changes. */
|
|
4
|
+
|
|
5
|
+
[data-product="courselit"] {
|
|
6
|
+
--primary: oklch(0.6 0.14 30);
|
|
7
|
+
--primary-foreground: oklch(0.99 0.005 85);
|
|
8
|
+
--primary-soft: oklch(0.6 0.14 30 / 0.12);
|
|
9
|
+
--primary-hover: oklch(0.55 0.14 30);
|
|
10
|
+
--ring: oklch(0.6 0.14 30);
|
|
11
|
+
}
|
|
12
|
+
.dark[data-product="courselit"], .dark [data-product="courselit"] {
|
|
13
|
+
--primary: oklch(0.72 0.13 30);
|
|
14
|
+
--primary-foreground: oklch(0.2 0.04 30);
|
|
15
|
+
--primary-soft: oklch(0.72 0.13 30 / 0.16);
|
|
16
|
+
--primary-hover: oklch(0.77 0.13 30);
|
|
17
|
+
--ring: oklch(0.72 0.13 30);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
[data-product="medialit"] {
|
|
21
|
+
--primary: oklch(0.6 0.14 300);
|
|
22
|
+
--primary-foreground: oklch(0.99 0.005 85);
|
|
23
|
+
--primary-soft: oklch(0.6 0.14 300 / 0.12);
|
|
24
|
+
--primary-hover: oklch(0.55 0.14 300);
|
|
25
|
+
--ring: oklch(0.6 0.14 300);
|
|
26
|
+
}
|
|
27
|
+
.dark[data-product="medialit"], .dark [data-product="medialit"] {
|
|
28
|
+
--primary: oklch(0.72 0.13 300);
|
|
29
|
+
--primary-foreground: oklch(0.2 0.04 300);
|
|
30
|
+
--primary-soft: oklch(0.72 0.13 300 / 0.16);
|
|
31
|
+
--primary-hover: oklch(0.77 0.13 300);
|
|
32
|
+
--ring: oklch(0.72 0.13 300);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-product="sendlit"] {
|
|
36
|
+
--primary: oklch(0.6 0.14 150);
|
|
37
|
+
--primary-foreground: oklch(0.99 0.005 85);
|
|
38
|
+
--primary-soft: oklch(0.6 0.14 150 / 0.12);
|
|
39
|
+
--primary-hover: oklch(0.55 0.14 150);
|
|
40
|
+
--ring: oklch(0.6 0.14 150);
|
|
41
|
+
}
|
|
42
|
+
.dark[data-product="sendlit"], .dark [data-product="sendlit"] {
|
|
43
|
+
--primary: oklch(0.72 0.13 150);
|
|
44
|
+
--primary-foreground: oklch(0.2 0.04 150);
|
|
45
|
+
--primary-soft: oklch(0.72 0.13 150 / 0.16);
|
|
46
|
+
--primary-hover: oklch(0.77 0.13 150);
|
|
47
|
+
--ring: oklch(0.72 0.13 150);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-product="frontlit"] {
|
|
51
|
+
--primary: oklch(0.6 0.14 230);
|
|
52
|
+
--primary-foreground: oklch(0.99 0.005 85);
|
|
53
|
+
--primary-soft: oklch(0.6 0.14 230 / 0.12);
|
|
54
|
+
--primary-hover: oklch(0.55 0.14 230);
|
|
55
|
+
--ring: oklch(0.6 0.14 230);
|
|
56
|
+
}
|
|
57
|
+
.dark[data-product="frontlit"], .dark [data-product="frontlit"] {
|
|
58
|
+
--primary: oklch(0.72 0.13 230);
|
|
59
|
+
--primary-foreground: oklch(0.2 0.04 230);
|
|
60
|
+
--primary-soft: oklch(0.72 0.13 230 / 0.16);
|
|
61
|
+
--primary-hover: oklch(0.77 0.13 230);
|
|
62
|
+
--ring: oklch(0.72 0.13 230);
|
|
63
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* CodeLit — typography tokens. One workhorse sans + one mono. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--font-sans: "Hanken Grotesk", ui-sans-serif, system-ui, sans-serif;
|
|
5
|
+
--font-mono: "Spline Sans Mono", ui-monospace, "SF Mono", monospace;
|
|
6
|
+
|
|
7
|
+
/* Type scale */
|
|
8
|
+
--text-display: 800 2.25rem/1.12 var(--font-sans); /* 36px, -0.02em */
|
|
9
|
+
--text-h1: 700 1.75rem/1.2 var(--font-sans); /* 28px, -0.02em */
|
|
10
|
+
--text-h2: 700 1.375rem/1.25 var(--font-sans); /* 22px, -0.015em */
|
|
11
|
+
--text-h3: 600 1.125rem/1.3 var(--font-sans); /* 18px */
|
|
12
|
+
--text-body: 400 0.9375rem/1.55 var(--font-sans); /* 15px */
|
|
13
|
+
--text-body-sm: 400 0.875rem/1.5 var(--font-sans); /* 14px */
|
|
14
|
+
--text-label: 600 0.8125rem/1.3 var(--font-sans); /* 13px */
|
|
15
|
+
--text-caption: 400 0.75rem/1.4 var(--font-sans); /* 12px */
|
|
16
|
+
--text-code: 400 0.8125rem/1.5 var(--font-mono); /* 13px */
|
|
17
|
+
--text-eyebrow: 500 0.6875rem/1.3 var(--font-mono); /* 11px mono, letter-spacing 0.08em, sentence case */
|
|
18
|
+
|
|
19
|
+
--tracking-tight: -0.02em;
|
|
20
|
+
--tracking-heading: -0.015em;
|
|
21
|
+
--tracking-eyebrow: 0.08em;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
margin: 0;
|
|
26
|
+
font: var(--text-body);
|
|
27
|
+
color: var(--foreground);
|
|
28
|
+
background: var(--background);
|
|
29
|
+
-webkit-font-smoothing: antialiased;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
a { color: var(--primary); text-decoration: none; }
|
|
33
|
+
a:hover { color: var(--primary-hover); text-decoration: underline; }
|
|
34
|
+
|
|
35
|
+
code, kbd, pre { font: var(--text-code); }
|