@crewlethq/ui 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Crewlet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @crewlethq/ui
2
+
3
+ Crewlet shared React components. Built on top of `@crewlethq/tokens` (CSS variables) and `@crewlethq/icons`.
4
+
5
+ ## Use it
6
+
7
+ ```tsx
8
+ import '@crewlethq/tokens/css'; // load CSS variables once at app entry
9
+ import '@crewlethq/tokens/css/fonts'; // self-hosted Inter and JetBrains Mono
10
+ import '@crewlethq/tokens/css/material-symbols'; // icon font the components render glyphs with
11
+ import '@crewlethq/ui/styles.css'; // component styles (or rely on per-component CSS imports)
12
+ import { Button, Card } from '@crewlethq/ui';
13
+ import { Icon } from '@crewlethq/icons';
14
+
15
+ <Card>
16
+ <Card.Body>
17
+ <Button variant="primary" leadingIcon={<Icon name="AgentReading" />}>
18
+ Open documentation
19
+ </Button>
20
+ <Button variant="outline">Cancel</Button>
21
+ </Card.Body>
22
+ </Card>
23
+ ```
24
+
25
+ The components render their glyphs as ligatures of the Material Symbols Outlined icon font inside `<span class="material-symbols-outlined">`. `@crewlethq/tokens/css/material-symbols` loads that font from Google Fonts (Apache License 2.0) and is the only import above that contacts a third-party host. Skip it when the app already loads the font under the same class name; see the `@crewlethq/tokens` README for details.
26
+
27
+ ## What is in here
28
+
29
+ | Area | Components |
30
+ | ---- | ---------- |
31
+ | Actions | `Button` (variants `primary`, `secondary`, `outline`, `tertiary`, `accent`, `danger`; sizes `small`, `medium`, `large`; shapes `square`, `pill`), `IconButton`, `Copyable`, `Kbd` |
32
+ | Forms | `Input`, `Textarea`, `Label`, `FormField`, `FormRow`, `Checkbox`, `Select`, `Tag`, `TagsInput`, `DateTimePicker`, `TimeWindowPicker`, `ImageUpload` |
33
+ | Data display | `DataTable`, `CopyableCell`, `Table`, `StatCard`, `PricingCard`, `Avatar`, `CodeBlock`, `Skeleton`, `Eyebrow` |
34
+ | Feedback and overlays | `Callout`, `Toaster`, `Modal`, `ConfirmModal`, `Popover` |
35
+ | Layout | `AppShell`, `Container`, `Section`, `Card`, `Tabs`, `Accordion` |
36
+
37
+ Storybook stories live under `apps/storybook/src/stories`.
38
+
39
+ Add new components under `src/<Name>/` with a `Component.tsx`, optional `Component.css`, and an `index.ts`. Re-export from `src/index.ts`.
40
+
41
+ ## Theming hooks
42
+
43
+ - **Light and dark palettes.** Import `@crewlethq/tokens/css/themes` and toggle `theme-light` or `theme-dark` on `<body>`. Every component reads the canonical `--color-*` variables, so no provider is needed.
44
+ - **Accent colour.** The selected, active and focus states read `--color-brand-accent` and its companions from `@crewlethq/tokens`: `--color-brand-accent-rgb` (the same colour as a comma-separated `r, g, b` triple, for translucent tints), `--color-brand-accent-hover`, `--color-brand-accent-active`, `--color-brand-accent-soft` and `--color-brand-accent-soft-strong`. Rebind them together to retint the components. `Select` menus, `Popover` panels and the `DateTimePicker` and `TimeWindowPicker` popovers are portaled to `<body>`, so declare the override on `<body>` (or `<html>`) for those panels to follow it.
45
+
46
+ ```css
47
+ /* Retint every accent state to the brand slate. */
48
+ body {
49
+ --color-brand-accent: var(--color-brand-slate);
50
+ --color-brand-accent-hover: var(--color-brand-slate);
51
+ --color-brand-accent-active: var(--color-brand-slate);
52
+ --color-brand-accent-rgb: 60, 64, 82;
53
+ --color-brand-accent-soft: rgba(60, 64, 82, 0.10);
54
+ --color-brand-accent-soft-strong: rgba(60, 64, 82, 0.32);
55
+ }
56
+ ```
57
+
58
+ - **Component variables.** A few components expose their own `--crewlet-*` variables for values that are not tokens, for example `--crewlet-data-table-active-sort-color`, `--crewlet-data-table-row-hover-bg` and `--crewlet-data-table-archived-tint-rgb` on the compact `DataTable`, and `--crewlet-avatar-tint-fg` for the initials on a tinted `Avatar`. Override them from a rule that targets the component's root.
59
+
60
+ The component stylesheets read only variables that `@crewlethq/tokens` emits or that the components declare themselves; `npm run lint` in this package enforces it.
61
+
62
+ ## Why CSS, not CSS-in-JS
63
+
64
+ The tokens package emits plain CSS variables. Plain CSS keeps every consumer (any bundler, server-rendered or not) compatible with no runtime overhead and no theme provider, and AI tools can read and modify the styles without a build step.
65
+
66
+ ## License
67
+
68
+ MIT. The license text is in [`LICENSE`](./LICENSE), which ships in the package. The Crewlet name, logo and character are trademarks and are not covered by that license.
@@ -0,0 +1,88 @@
1
+ /*
2
+ * Accordion, a stacked disclosure list for FAQs and dense settings. One
3
+ * hairline rule between items, a chevron that rotates on open, and a panel
4
+ * that is fully removed from the a11y tree when collapsed.
5
+ */
6
+
7
+ .crewlet-accordion {
8
+ display: flex;
9
+ flex-direction: column;
10
+ border-top: 1px solid var(--color-border-default);
11
+ }
12
+
13
+ .crewlet-accordion__item {
14
+ border-bottom: 1px solid var(--color-border-default);
15
+ }
16
+
17
+ .crewlet-accordion__heading {
18
+ margin: 0;
19
+ font: inherit;
20
+ }
21
+
22
+ .crewlet-accordion__trigger {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: var(--spacing-4);
27
+ width: 100%;
28
+ padding: var(--spacing-5) 0;
29
+ background: none;
30
+ border: none;
31
+ cursor: pointer;
32
+ text-align: left;
33
+ color: var(--color-text-primary);
34
+ font-family: var(--font-family-display);
35
+ font-size: var(--font-size-lg);
36
+ font-weight: var(--font-weight-medium);
37
+ line-height: var(--font-line-height-snug);
38
+ transition: color var(--motion-duration-base) var(--motion-easing-out);
39
+ }
40
+
41
+ .crewlet-accordion__trigger:hover {
42
+ color: var(--color-brand-accent);
43
+ }
44
+
45
+ .crewlet-accordion__trigger:focus-visible {
46
+ outline: none;
47
+ box-shadow: var(--shadow-focus);
48
+ border-radius: var(--radius-sm);
49
+ }
50
+
51
+ .crewlet-accordion__item.is-disabled .crewlet-accordion__trigger {
52
+ cursor: not-allowed;
53
+ color: var(--color-text-muted);
54
+ }
55
+
56
+ .crewlet-accordion__title {
57
+ min-width: 0;
58
+ }
59
+
60
+ .crewlet-accordion__icon {
61
+ flex-shrink: 0;
62
+ font-size: 24px;
63
+ color: var(--color-text-tertiary);
64
+ transition: transform var(--motion-duration-base) var(--motion-easing-out);
65
+ }
66
+
67
+ .crewlet-accordion__item.is-open .crewlet-accordion__icon {
68
+ transform: rotate(180deg);
69
+ color: var(--color-brand-accent);
70
+ }
71
+
72
+ .crewlet-accordion__panel {
73
+ color: var(--color-text-secondary);
74
+ font-size: var(--font-size-md);
75
+ line-height: var(--font-line-height-relaxed, 1.7);
76
+ }
77
+
78
+ .crewlet-accordion__panel-inner {
79
+ padding: 0 0 var(--spacing-5);
80
+ max-width: 68ch;
81
+ }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .crewlet-accordion__icon,
85
+ .crewlet-accordion__trigger {
86
+ transition: none;
87
+ }
88
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ * AppShell, layout shell for console apps.
3
+ *
4
+ * Passive composition: the sidebar and topbar slots position
5
+ * themselves (typically `position: fixed`); AppShell only reserves
6
+ * the gutters the fixed elements overlay via margin / padding on
7
+ * the content column, so an application can adopt the shell without
8
+ * restyling a sidebar or header it already positions itself.
9
+ *
10
+ * Layout dimensions are exposed as CSS custom properties on the
11
+ * root so styles inside slot children can read them.
12
+ */
13
+
14
+ .crewlet-app-shell {
15
+ display: flex;
16
+ height: 100vh;
17
+ overflow-x: hidden;
18
+ overflow-y: visible;
19
+ --crewlet-app-shell-sidebar-width: 280px;
20
+ --crewlet-app-shell-topbar-height: 64px;
21
+ }
22
+
23
+ .crewlet-app-shell__column {
24
+ flex: 1;
25
+ min-width: 0;
26
+ display: flex;
27
+ flex-direction: column;
28
+ margin-left: var(--crewlet-app-shell-sidebar-width);
29
+ padding-top: var(--crewlet-app-shell-topbar-height);
30
+ height: 100vh;
31
+ overflow-x: hidden;
32
+ overflow-y: visible;
33
+ }
34
+
35
+ .crewlet-app-shell__main {
36
+ flex: 1;
37
+ padding: 40px 0;
38
+ overflow-y: auto;
39
+ overflow-x: visible;
40
+ min-height: 0;
41
+ /*
42
+ * Reserve space for the vertical scrollbar on every page so the
43
+ * tab strip, headers, and content stay anchored at the same x
44
+ * position when navigating between a tall page (long form, large
45
+ * table) and a short page (a card or two). Without this gutter,
46
+ * the scrollbar appears or disappears between routes and shifts
47
+ * every element horizontally by the scrollbar's width, which
48
+ * reads as the page "jumping" on every navigation.
49
+ */
50
+ scrollbar-gutter: stable;
51
+ }
@@ -0,0 +1,67 @@
1
+ /*
2
+ * Avatar, round identity badge.
3
+ *
4
+ * The initials fallback
5
+ * uses the defined brand-accent surface and its paired on-brand text token so
6
+ * the dark, light, and system themes all keep an accessible contrast ratio.
7
+ */
8
+
9
+ .crewlet-avatar {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ flex-shrink: 0;
14
+ overflow: hidden;
15
+ object-fit: cover;
16
+ font-family: var(--font-family-sans);
17
+ font-weight: 600;
18
+ line-height: 1;
19
+ user-select: none;
20
+ }
21
+
22
+ /* Shape modifiers. "circle" is the round identity badge; "square" keeps
23
+ rounded corners (matching the Card radius) so wide marks such as company
24
+ logos are not cropped into a circle. */
25
+ .crewlet-avatar--circle {
26
+ border-radius: var(--radius-circle, 50%);
27
+ }
28
+
29
+ .crewlet-avatar--square {
30
+ border-radius: var(--radius-lg, 12px);
31
+ }
32
+
33
+ .crewlet-avatar--image {
34
+ display: block;
35
+ object-fit: cover;
36
+ }
37
+
38
+ .crewlet-avatar--fallback {
39
+ background: var(--color-brand-accent);
40
+ color: var(--color-text-on-brand);
41
+ text-transform: uppercase;
42
+ }
43
+
44
+ /*
45
+ * Identity tints. When a colorSeed is supplied, the initials fallback picks
46
+ * one of these fixed backgrounds by a stable hash of the seed, so each
47
+ * identity keeps a distinct, consistent colour across the product (agent
48
+ * rosters, member lists). The palette is chosen for a legible contrast ratio
49
+ * against white initials (overridable through --crewlet-avatar-tint-fg),
50
+ * and each tint overrides the plain
51
+ * brand-accent fallback above. Ordering matters: these rules follow
52
+ * .crewlet-avatar--fallback so the tint wins at equal specificity.
53
+ */
54
+ .crewlet-avatar--tint {
55
+ color: var(--crewlet-avatar-tint-fg, #ffffff);
56
+ }
57
+
58
+ .crewlet-avatar--tint-0 { background: #2563eb; }
59
+ .crewlet-avatar--tint-1 { background: #7c3aed; }
60
+ .crewlet-avatar--tint-2 { background: #c026d3; }
61
+ .crewlet-avatar--tint-3 { background: #db2777; }
62
+ .crewlet-avatar--tint-4 { background: #dc2626; }
63
+ .crewlet-avatar--tint-5 { background: #ea580c; }
64
+ .crewlet-avatar--tint-6 { background: #ca8a04; }
65
+ .crewlet-avatar--tint-7 { background: #16a34a; }
66
+ .crewlet-avatar--tint-8 { background: #0d9488; }
67
+ .crewlet-avatar--tint-9 { background: #0284c7; }
@@ -0,0 +1,174 @@
1
+ /*
2
+ * Button styles. Colour properties go through the @crewlethq/tokens
3
+ * variables so the dark, light and system themes pick up the right
4
+ * palette without a per-theme rule here.
5
+ *
6
+ * Each size fixes a min-height (28, 32 and 44px) so a row of mixed
7
+ * buttons, with and without icons, keeps one height. IconButton's
8
+ * 28px size matches the small button, so check the IconButton and
9
+ * toolbar stories in Storybook when you change a size.
10
+ */
11
+
12
+ .crewlet-btn {
13
+ display: inline-flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ gap: 6px;
17
+ border: 1px solid transparent;
18
+ border-radius: 8px;
19
+ font-family: var(--font-family-sans);
20
+ font-weight: 600;
21
+ letter-spacing: -0.005em;
22
+ text-decoration: none;
23
+ white-space: nowrap;
24
+ cursor: pointer;
25
+ transition: background 150ms ease, color 150ms ease, border-color 150ms ease, box-shadow 150ms ease;
26
+ }
27
+
28
+ .crewlet-btn:focus-visible {
29
+ outline: none;
30
+ box-shadow: var(--shadow-focus, 0 0 0 3px rgba(84, 105, 212, 0.32));
31
+ }
32
+
33
+ .crewlet-btn:disabled {
34
+ opacity: 0.4;
35
+ cursor: not-allowed;
36
+ pointer-events: none;
37
+ }
38
+
39
+ /* ── Sizes (padding + font + min-height) ─────────────────────────── */
40
+ .crewlet-btn--small { padding: 5px 12px; font-size: 12px; min-height: 28px; }
41
+ .crewlet-btn--medium { padding: 7px 14px; font-size: 13px; min-height: 32px; }
42
+ .crewlet-btn--large { padding: 9px 18px; font-size: 15px; min-height: 44px; }
43
+
44
+ /* ── Shape modifiers ────────────────────────────────────────────── */
45
+ .crewlet-btn--square { border-radius: 8px; }
46
+ .crewlet-btn--pill { border-radius: 100px; gap: 6px; }
47
+
48
+ .crewlet-btn--pill.crewlet-btn--small { padding: 6px 16px; }
49
+ .crewlet-btn--pill.crewlet-btn--medium { padding: 9px 22px; }
50
+ .crewlet-btn--pill.crewlet-btn--large { padding: 11px 28px; }
51
+
52
+ /*
53
+ * Pill buttons paired with Material Symbols icons read better when
54
+ * the icon drops from its default 20px to 16px so "← Back" / "Next →"
55
+ * pairs sit as a unit.
56
+ */
57
+ .crewlet-btn--pill .material-symbols-outlined {
58
+ font-size: 16px;
59
+ }
60
+
61
+ /* ── Primary: solid white CTA, black text (dark theme); flips to
62
+ black-on-white in the light theme through the token overrides. ── */
63
+ .crewlet-btn--primary {
64
+ background: var(--color-brand-primary, #ffffff);
65
+ color: var(--color-text-on-brand, #000000);
66
+ border-color: var(--color-brand-primary, #ffffff);
67
+ }
68
+ .crewlet-btn--primary:hover:not(:disabled) {
69
+ background: var(--color-brand-primary-hover, rgba(255, 255, 255, 0.9));
70
+ border-color: var(--color-brand-primary-hover, rgba(255, 255, 255, 0.9));
71
+ }
72
+ .crewlet-btn--primary:active:not(:disabled) {
73
+ background: var(--color-brand-primary-active, rgba(255, 255, 255, 0.82));
74
+ border-color: var(--color-brand-primary-active, rgba(255, 255, 255, 0.82));
75
+ }
76
+
77
+ /* ── Secondary: ghost with visible border. ──────────────────────── */
78
+ .crewlet-btn--secondary {
79
+ background: var(--color-surface-subtle);
80
+ color: var(--color-text-primary);
81
+ border-color: var(--color-border-default);
82
+ }
83
+ .crewlet-btn--secondary:hover:not(:disabled) {
84
+ background: var(--color-surface-elevated, var(--color-surface-subtle));
85
+ border-color: var(--color-border-strong);
86
+ }
87
+
88
+ /* ── Outline: lavender accent. Branded secondary CTA. ───────────── */
89
+ .crewlet-btn--outline {
90
+ background: transparent;
91
+ color: var(--color-brand-accent, #5469d4);
92
+ border-color: var(--color-brand-accent, #5469d4);
93
+ }
94
+ .crewlet-btn--outline:hover:not(:disabled) {
95
+ background: var(--color-brand-accent-soft, rgba(84, 105, 212, 0.10));
96
+ border-color: var(--color-brand-accent, #5469d4);
97
+ }
98
+
99
+ /* ── Tertiary: borderless ghost. ─────────────────────────────────── */
100
+ .crewlet-btn--tertiary {
101
+ background: transparent;
102
+ color: var(--color-text-secondary);
103
+ border-color: transparent;
104
+ }
105
+ .crewlet-btn--tertiary:hover:not(:disabled) {
106
+ background: var(--color-surface-elevated, var(--color-surface-subtle));
107
+ color: var(--color-text-primary);
108
+ }
109
+
110
+ /* ── Accent: solid brand accent CTA. Used when the page already has
111
+ a white "primary" and a branded "yes do this" still belongs on
112
+ the surface, for example the action inside a toast. ────────── */
113
+ .crewlet-btn--accent {
114
+ background: var(--color-brand-accent, #5469d4);
115
+ color: #ffffff;
116
+ border-color: var(--color-brand-accent, #5469d4);
117
+ }
118
+ .crewlet-btn--accent:hover:not(:disabled) {
119
+ background: var(--color-brand-accent-hover, #6478e0);
120
+ border-color: var(--color-brand-accent-hover, #6478e0);
121
+ }
122
+ .crewlet-btn--accent:active:not(:disabled) {
123
+ background: var(--color-brand-accent-active, #4a5cc2);
124
+ border-color: var(--color-brand-accent-active, #4a5cc2);
125
+ }
126
+
127
+ /* ── Danger: solid red for destructive confirmation. ────────────── */
128
+ .crewlet-btn--danger {
129
+ background: var(--color-feedback-danger, #f43f5e);
130
+ color: #ffffff;
131
+ border-color: var(--color-feedback-danger, #f43f5e);
132
+ }
133
+ .crewlet-btn--danger:hover:not(:disabled) {
134
+ background: var(--color-feedback-danger-hover, #e11d48);
135
+ border-color: var(--color-feedback-danger-hover, #e11d48);
136
+ }
137
+
138
+ .crewlet-btn__icon { display: inline-flex; align-items: center; }
139
+ .crewlet-btn__label { display: inline-flex; align-items: center; }
140
+
141
+ /*
142
+ * Pending state. The spinner takes the leading icon's place so the
143
+ * label neither moves nor changes width when an action starts, which is
144
+ * what makes a button feel like it is working rather than replaced.
145
+ */
146
+ .crewlet-btn__spinner {
147
+ display: inline-block;
148
+ width: 1em;
149
+ height: 1em;
150
+ border: 2px solid currentColor;
151
+ border-right-color: transparent;
152
+ border-radius: 50%;
153
+ opacity: 0.7;
154
+ animation: crewlet-btn-spin 0.6s linear infinite;
155
+ flex: none;
156
+ }
157
+
158
+ @keyframes crewlet-btn-spin {
159
+ to { transform: rotate(360deg); }
160
+ }
161
+
162
+ /* A disabled button already reads as unavailable; a loading one should
163
+ still read as this button, mid-action. */
164
+ .crewlet-btn--loading {
165
+ cursor: progress;
166
+ }
167
+
168
+ /* A spinner that cannot spin should not pretend to. The ring still
169
+ reads as pending without the motion. */
170
+ @media (prefers-reduced-motion: reduce) {
171
+ .crewlet-btn__spinner {
172
+ animation: none;
173
+ }
174
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Callout, inline status banner. One tinted surface per feedback
3
+ * tone, readable on dark surfaces without stealing focus from the
4
+ * content it annotates.
5
+ */
6
+
7
+ .crewlet-callout {
8
+ display: flex;
9
+ align-items: flex-start;
10
+ gap: 10px;
11
+ border: 1px solid;
12
+ border-radius: 10px;
13
+ padding: 12px 16px;
14
+ font-size: 13px;
15
+ line-height: 1.5;
16
+ }
17
+
18
+ .crewlet-callout__icon {
19
+ display: inline-flex;
20
+ margin-top: 1px;
21
+ }
22
+
23
+ .crewlet-callout__content { min-width: 0; }
24
+
25
+ .crewlet-callout__title {
26
+ font-weight: 600;
27
+ margin-right: 6px;
28
+ }
29
+
30
+ .crewlet-callout--info {
31
+ border-color: color-mix(in srgb, var(--color-feedback-info) 30%, transparent);
32
+ background: color-mix(in srgb, var(--color-feedback-info) 9%, transparent);
33
+ color: color-mix(in srgb, var(--color-feedback-info) 55%, var(--color-text-primary));
34
+ }
35
+
36
+ .crewlet-callout--success {
37
+ border-color: color-mix(in srgb, var(--color-feedback-success) 32%, transparent);
38
+ background: color-mix(in srgb, var(--color-feedback-success) 9%, transparent);
39
+ color: color-mix(in srgb, var(--color-feedback-success) 55%, var(--color-text-primary));
40
+ }
41
+
42
+ .crewlet-callout--warning {
43
+ border-color: color-mix(in srgb, var(--color-feedback-warning) 32%, transparent);
44
+ background: color-mix(in srgb, var(--color-feedback-warning) 9%, transparent);
45
+ color: color-mix(in srgb, var(--color-feedback-warning) 60%, var(--color-text-primary));
46
+ }
47
+
48
+ .crewlet-callout--danger {
49
+ border-color: color-mix(in srgb, var(--color-feedback-danger) 30%, transparent);
50
+ background: color-mix(in srgb, var(--color-feedback-danger) 9%, transparent);
51
+ color: color-mix(in srgb, var(--color-feedback-danger) 45%, var(--color-text-primary));
52
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Card, surface primitive with optional Header / Body / Footer slots.
3
+ * The 12px radius is shared with PricingCard so a card grid and a plain
4
+ * card read as one family on the same page.
5
+ *
6
+ * Variants: default sits one step above the page on the subtle surface,
7
+ * subtle sits flush with the page background behind its border,
8
+ * outlined is transparent with a strong border, and elevated lifts off
9
+ * the page with the elevated surface and a shadow.
10
+ */
11
+
12
+ .crewlet-card {
13
+ background-color: var(--color-surface-subtle);
14
+ border: 1px solid var(--color-border-default);
15
+ border-radius: 12px;
16
+ color: var(--color-text-primary);
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ .crewlet-card--default { background-color: var(--color-surface-subtle); }
22
+ .crewlet-card--subtle { background-color: var(--color-surface-background); }
23
+ .crewlet-card--outlined {
24
+ background-color: transparent;
25
+ border-color: var(--color-border-strong);
26
+ }
27
+ .crewlet-card--elevated {
28
+ background-color: var(--color-surface-elevated, var(--color-surface-subtle));
29
+ box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
30
+ }
31
+
32
+ .crewlet-card--p-sm { padding: 12px 16px; }
33
+ .crewlet-card--p-md { padding: 20px 24px; }
34
+ .crewlet-card--p-lg { padding: 24px 28px; }
35
+
36
+ .crewlet-card.is-interactive {
37
+ cursor: pointer;
38
+ transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
39
+ }
40
+ .crewlet-card.is-interactive:hover {
41
+ border-color: var(--color-border-strong);
42
+ transform: translateY(-1px);
43
+ box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
44
+ }
45
+ .crewlet-card.is-interactive:focus-visible {
46
+ outline: none;
47
+ box-shadow: var(--shadow-focus, 0 0 0 3px rgba(84, 105, 212, 0.32));
48
+ }
49
+
50
+ .crewlet-card__header {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: space-between;
54
+ gap: 16px;
55
+ margin-bottom: 16px;
56
+ }
57
+
58
+ .crewlet-card__body {
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: 12px;
62
+ min-height: 0;
63
+ }
64
+
65
+ .crewlet-card__footer {
66
+ display: flex;
67
+ justify-content: flex-end;
68
+ gap: 8px;
69
+ margin-top: 16px;
70
+ padding-top: 16px;
71
+ border-top: 1px solid var(--color-border-default);
72
+ }
73
+
74
+ .crewlet-card__title {
75
+ font-size: 15px;
76
+ font-weight: 600;
77
+ color: var(--color-text-primary);
78
+ margin: 0;
79
+ }
80
+
81
+ .crewlet-card__description {
82
+ font-size: 13px;
83
+ color: var(--color-text-secondary);
84
+ margin: 0;
85
+ line-height: 1.5;
86
+ }
@@ -0,0 +1,58 @@
1
+ /*
2
+ * Checkbox, a box and its label as one target.
3
+ *
4
+ * The label is part of the control rather than text beside it, so the
5
+ * whole thing is clickable and the description is announced with it.
6
+ */
7
+
8
+ .crewlet-checkbox {
9
+ display: flex;
10
+ align-items: flex-start;
11
+ gap: 10px;
12
+ }
13
+
14
+ .crewlet-checkbox__control {
15
+ /* Aligns the box with the first line of the label rather than the
16
+ block, which drifts as soon as a description wraps. */
17
+ margin: 2px 0 0;
18
+ width: 16px;
19
+ height: 16px;
20
+ flex: 0 0 auto;
21
+ accent-color: var(--color-brand-primary);
22
+ cursor: pointer;
23
+ }
24
+
25
+ .crewlet-checkbox--danger .crewlet-checkbox__control {
26
+ accent-color: var(--color-feedback-danger);
27
+ }
28
+
29
+ .crewlet-checkbox__text {
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 2px;
33
+ cursor: pointer;
34
+ user-select: none;
35
+ }
36
+
37
+ .crewlet-checkbox__label {
38
+ font-size: 14px;
39
+ line-height: 1.4;
40
+ color: var(--color-text-primary);
41
+ }
42
+
43
+ .crewlet-checkbox__description {
44
+ font-size: 13px;
45
+ line-height: 1.45;
46
+ color: var(--color-text-secondary);
47
+ }
48
+
49
+ .crewlet-checkbox.is-disabled .crewlet-checkbox__control,
50
+ .crewlet-checkbox.is-disabled .crewlet-checkbox__text {
51
+ cursor: not-allowed;
52
+ opacity: 0.6;
53
+ }
54
+
55
+ .crewlet-checkbox__control:focus-visible {
56
+ outline: 2px solid var(--color-brand-primary);
57
+ outline-offset: 2px;
58
+ }