@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.
Files changed (44) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +68 -0
  4. package/package.json +44 -0
  5. package/src/assets/logo-codelit.svg +1 -0
  6. package/src/assets/logo-courselit.svg +1 -0
  7. package/src/assets/logo-frontlit.svg +1 -0
  8. package/src/assets/logo-medialit.svg +1 -0
  9. package/src/assets/logo-sendlit.svg +1 -0
  10. package/src/components/actions/Button.prompt.md +8 -0
  11. package/src/components/actions/Button.tsx +27 -0
  12. package/src/components/actions/IconButton.prompt.md +7 -0
  13. package/src/components/actions/IconButton.tsx +28 -0
  14. package/src/components/display/Badge.prompt.md +8 -0
  15. package/src/components/display/Badge.tsx +19 -0
  16. package/src/components/display/Card.prompt.md +9 -0
  17. package/src/components/display/Card.tsx +26 -0
  18. package/src/components/display/Tabs.prompt.md +5 -0
  19. package/src/components/display/Tabs.tsx +31 -0
  20. package/src/components/feedback/Dialog.prompt.md +6 -0
  21. package/src/components/feedback/Dialog.tsx +35 -0
  22. package/src/components/feedback/Toast.prompt.md +5 -0
  23. package/src/components/feedback/Toast.tsx +27 -0
  24. package/src/components/feedback/Tooltip.prompt.md +7 -0
  25. package/src/components/feedback/Tooltip.tsx +26 -0
  26. package/src/components/forms/Checkbox.prompt.md +5 -0
  27. package/src/components/forms/Checkbox.tsx +30 -0
  28. package/src/components/forms/Input.prompt.md +8 -0
  29. package/src/components/forms/Input.tsx +40 -0
  30. package/src/components/forms/Radio.prompt.md +6 -0
  31. package/src/components/forms/Radio.tsx +31 -0
  32. package/src/components/forms/Select.prompt.md +7 -0
  33. package/src/components/forms/Select.tsx +41 -0
  34. package/src/components/forms/Switch.prompt.md +5 -0
  35. package/src/components/forms/Switch.tsx +23 -0
  36. package/src/components.css +182 -0
  37. package/src/index.ts +13 -0
  38. package/src/styles.css +6 -0
  39. package/src/tokens/colors.css +83 -0
  40. package/src/tokens/fonts.css +2 -0
  41. package/src/tokens/layout.css +37 -0
  42. package/src/tokens/products.css +63 -0
  43. package/src/tokens/typography.css +35 -0
  44. 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,6 @@
1
+ Radio option matching Checkbox styling; group options with the same `name`.
2
+
3
+ ```jsx
4
+ <Radio name="visibility" label="Public" defaultChecked />
5
+ <Radio name="visibility" label="Unlisted" description="Only people with the link." />
6
+ ```
@@ -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,7 @@
1
+ Native select styled like Input, with a chevron affordance.
2
+
3
+ ```jsx
4
+ <Select label="Audience" options={["Everyone", "Enrolled students", "Team only"]} />
5
+ ```
6
+
7
+ Options accept strings or `{value, label}`.
@@ -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,5 @@
1
+ Toggle for immediate on/off settings (use Checkbox for form consent).
2
+
3
+ ```jsx
4
+ <Switch label="Auto-publish new lessons" defaultChecked />
5
+ ```
@@ -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,6 @@
1
+ @import "./tokens/fonts.css";
2
+ @import "./tokens/colors.css";
3
+ @import "./tokens/products.css";
4
+ @import "./tokens/typography.css";
5
+ @import "./tokens/layout.css";
6
+ @import "./components.css";
@@ -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,2 @@
1
+ /* CodeLit — webfonts (Google Fonts CDN; no binaries vendored) */
2
+ @import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&family=Spline+Sans+Mono:wght@400;500;600&display=swap');
@@ -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); }