@greendrake/theme 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eugene Greendrake
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,92 @@
1
+ # @greendrake/theme
2
+
3
+ Design tokens, document-level element styling (links, lists, quotes, body defaults), the global spinner rules, the `light-dark` theming mixin, and an opt-in default dashboard palette.
4
+
5
+ ## Usage
6
+
7
+ ```scss
8
+ // Base: css-reset + tokens + document styling (side-effectful)
9
+ @use '@greendrake/theme' as *;
10
+
11
+ // Opt-in: the default dashboard light/dark palette
12
+ @use '@greendrake/theme/dashboard-palette';
13
+
14
+ // App-specific palette instead:
15
+ @include light-dark(
16
+ (
17
+ --font-color: #222
18
+ // ...
19
+ ),
20
+ (
21
+ --font-color: #eee
22
+ // ...
23
+ )
24
+ );
25
+ ```
26
+
27
+ Mixins exported by the root entry: `light-dark($light, $dark)`, `button-colors`, `overlay-icon-button($size)`, `wider-than-max-content` (plus `$max-content-width`). Framework-free SCSS primitives (breakpoints, z-layers, icon masks, font-face generation) live below this package in `@greendrake/scss-kit`.
28
+
29
+ ## CSS custom property contract
30
+
31
+ `@greendrake/ui` components render correctly only when the custom properties below are defined. This package's two entries cover part of the contract; the rest is the consuming app's responsibility (typically set in its palette maps or `:root`).
32
+
33
+ ### Defined by `@greendrake/theme` (root entry)
34
+
35
+ | Property | Value | Notes |
36
+ | --- | --- | --- |
37
+ | `--space-1` … `--space-6` | 4/8/12/16/24/28px | spacing scale; `--space-1`…`--space-4` are read by ui components |
38
+ | `--font-xs` … `--font-2xl` | 11/13/16/20/24/32px | font-size scale; `--font-xs` and `--font-md` are read by ui components |
39
+ | `--overlay-button-bg`, `--overlay-button-bg-hover` | `rgb(0 0 0 / 60%)`, `rgb(0 0 0 / 85%)` | consumed by the `overlay-icon-button` mixin |
40
+ | `--error-color` | `#ff4800` | semantic status color for app styling (not read by ui components) |
41
+ | `--warning-color` | `#890095` | semantic status color for app styling (not read by ui components) |
42
+ | `--accent-color` | `#0aa13e` | read by ui components (checkboxes, highlights) |
43
+
44
+ ### Defined by `@greendrake/theme/dashboard-palette` (opt-in, light and dark values)
45
+
46
+ | Property | Light | Dark |
47
+ | --- | --- | --- |
48
+ | `--font-color` | `#222` | `#eee` |
49
+ | `--background-color` | `#ddd` | `#222` |
50
+ | `--form-background` | `#f5f5f5` | `#222` |
51
+ | `--form-background-hover` | `#fff` | `#000` |
52
+ | `--border-color` | `#555` | `#aaa` |
53
+ | `--border-color-light` | `#999` | `#666` |
54
+ | `--border-color-hover` | `#000` | `#fff` |
55
+ | `--link-color-hover` | `#000` | `#fff` |
56
+ | `--light-switch` | moonstar icon SVG | sun icon SVG |
57
+ | `--spinner-color` | `#ccc` | `#ccc` |
58
+ | `--page-background` | `#e6e6e6` | `#202020` |
59
+ | `--page-vignette-color` | `#cdcdcd` | `#0c0c0c` |
60
+
61
+ The `--page-*` pair feeds the `page-vignette` mixin (a flat canvas tone with a box-shadow edge falloff — deliberately not a radial gradient, whose page-sized form Firefox can mis-render as a bright dot at its centre). The dashboard preset emits it; an app on a palette of its own defines the pair and includes the mixin itself.
62
+
63
+ Apps not using the dashboard palette must define at least the `--font-color`/`--background-color`/`--form-background*`/`--border-color*`/`--spinner-color` group themselves — ui components read them with no fallback.
64
+
65
+ ### Required from the consuming app (defined by neither entry, read with no fallback)
66
+
67
+ | Property | Read by |
68
+ | --- | --- |
69
+ | `--border-radius` | form controls, modals, tables, `overlay-icon-button` |
70
+ | `--icon-size` | `@greendrake/scss-kit` `icon` mixin, icon-bearing ui components |
71
+ | `--input-background` | text inputs |
72
+ | `--line-height-normal` | text layout in ui components |
73
+ | `--modal-background` | modal window |
74
+ | `--modal-border-radius` | modal window |
75
+ | `--spinner-side-color` | global `.spinner` rule, `img-spinner` mixin (typically `var(--form-background)`) |
76
+ | `--link-color` | document link styling in this package |
77
+ | `--blockquote-colour` | blockquote styling in this package |
78
+ | `--button-background` | `button-colors` mixin |
79
+
80
+ ### Optional (ui components read them with a fallback)
81
+
82
+ `--cell-padding`, `--circular-progress-arc`, `--circular-progress-label`, `--circular-progress-track`, `--list-row-padding`, `--modal-backdrop-filter`, `--modal-box-shadow`, `--modal-button-min-width`, `--modal-max-width`, `--modal-window-backdrop-filter`, `--row-border-bottom`, `--row-select-color`, `--row-select-color-hover`, `--tree-line-color`.
83
+
84
+ Component-scoped properties with local defaults (`--checkbox-size`, `--switch-width`, `--switch-height`, `--switch-knob`) are documented per component in `@greendrake/ui`.
85
+
86
+ ## Global spinner contract
87
+
88
+ This package defines the `spinner` keyframes and the `.spinner` / `body.spinner` / `div.spinner` rules that `@greendrake/ui`'s async controls and `@greendrake/scss-kit`'s `img-spinner` mixin rely on. They consume `--spinner-color` (palette) and `--spinner-side-color` (app).
89
+
90
+ ## Not included
91
+
92
+ Tooltip (tippy) skin and select-control skin ship with the components that need them in `@greendrake/ui`; this package styles only plain document elements.
package/_index.scss ADDED
@@ -0,0 +1,174 @@
1
+ // This entry EMITS global CSS (the reset, base element styles, the :root
2
+ // design tokens). It is the app-level base — `@use` it ONCE from the app's
3
+ // main.scss. A component that needs only a mixin must `@use
4
+ // '@greendrake/theme/mixins'` (side-effect-free); importing this module from a
5
+ // per-component <style> re-emits the reset into a late stylesheet that
6
+ // overrides the app's own form-control skin.
7
+ @use '@greendrake/css-reset';
8
+ @use '@greendrake/scss-kit' as kit;
9
+ @use 'mixins';
10
+ @forward 'mixins';
11
+
12
+ :root {
13
+ // Feeds ui's TheWrap width cap; single-sourced from the SCSS constant the
14
+ // wider-than-max-content mixin also uses.
15
+ --max-content-width: #{mixins.$max-content-width};
16
+ --space-1: 4px;
17
+ --space-2: 8px;
18
+ --space-3: 12px;
19
+ --space-4: 16px;
20
+ --space-5: 24px;
21
+ --space-6: 28px;
22
+ --font-xs: 11px;
23
+ --font-sm: 13px;
24
+ --font-md: 16px;
25
+ --font-lg: 20px;
26
+ --font-xl: 24px;
27
+ --font-2xl: 32px;
28
+
29
+ // The safe-area insets, as custom properties because that is the only form
30
+ // both platforms can deliver — everything downstream reads these and stays
31
+ // platform-agnostic. iOS and browsers resolve them from env() right here.
32
+ // The Android shell cannot: Chromium's env() reports display cutouts alone,
33
+ // never the system bars, so it is 0 on a phone whose status bar merely
34
+ // overlays the WebView — which from Android 15 is every phone, the window
35
+ // being edge-to-edge with no way to opt out. There Capacitor's built-in
36
+ // SystemBars plugin writes the real window insets onto <html> as inline
37
+ // styles of these same names, and inline beats this rule, so the override
38
+ // needs nothing declared for it. (It writes 0 in the case where it has
39
+ // already padded the WebView itself — either way these stay the insets the
40
+ // page must still spend.)
41
+ --safe-area-inset-top: env(safe-area-inset-top, 0px);
42
+ --safe-area-inset-right: env(safe-area-inset-right, 0px);
43
+ --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
44
+ --safe-area-inset-left: env(safe-area-inset-left, 0px);
45
+
46
+ --overlay-button-bg: rgb(0 0 0 / 60%);
47
+ --overlay-button-bg-hover: rgb(0 0 0 / 85%);
48
+
49
+ --error-color: #ff4800;
50
+ --warning-color: #890095;
51
+ --accent-color: #0aa13e;
52
+
53
+ // Single source of truth for spinner ring size — the default centred
54
+ // spinner shown at the middle of a loading UI. The small in-control variant
55
+ // overrides this (see button.spinner in form-controls).
56
+ --spinner-size: 40px;
57
+ }
58
+
59
+ body {
60
+ color: var(--font-color);
61
+ font-size: var(--font-md);
62
+ min-height: 100dvh;
63
+ display: flex;
64
+ justify-content: center;
65
+ }
66
+
67
+ p,
68
+ ol,
69
+ ul,
70
+ li {
71
+ margin: 6px 0;
72
+ }
73
+
74
+ p {
75
+ font-size: var(--font-md);
76
+ }
77
+
78
+ a,
79
+ a span,
80
+ a em {
81
+ color: var(--link-color);
82
+ }
83
+
84
+ a[href^='http'] {
85
+ padding-inline-end: 18px;
86
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'%3E%3Cg stroke='%23666' stroke-width='1.5' fill='none' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='17 13.5 17 19.5 5 19.5 5 7.5 11 7.5'%3E%3C/polyline%3E%3Cpath d='M14,4.5 L20,4.5 L20,10.5 M20,4.5 L11,13.5'%3E%3C/path%3E%3C/g%3E%3C/svg%3E");
87
+ background-repeat: no-repeat;
88
+ // background-position takes no logical keywords, so the RTL side is stated.
89
+ background-position: right center;
90
+ background-size: 16px;
91
+ [dir='rtl'] & {
92
+ background-position: left center;
93
+ }
94
+ }
95
+
96
+ ul {
97
+ list-style-type: square;
98
+ }
99
+
100
+ ol,
101
+ ul {
102
+ padding-inline-start: 32px;
103
+ }
104
+
105
+ blockquote {
106
+ border-inline-start: 8px solid #b8b5b9;
107
+ padding-inline-start: var(--space-4);
108
+ margin-inline-start: var(--space-4);
109
+ color: var(--blockquote-colour);
110
+ p,
111
+ em,
112
+ strong,
113
+ li {
114
+ color: var(--blockquote-colour);
115
+ }
116
+ }
117
+
118
+ .withUnderQuote p:last-child {
119
+ color: var(--font-color);
120
+ }
121
+
122
+ .hidden {
123
+ visibility: hidden;
124
+ }
125
+
126
+ .display-none {
127
+ display: none;
128
+ }
129
+
130
+ .contents {
131
+ display: contents;
132
+ }
133
+
134
+ .disabled,
135
+ [inert] {
136
+ @include kit.disabled;
137
+ }
138
+
139
+ @keyframes spinner {
140
+ to {
141
+ transform: rotate(360deg);
142
+ }
143
+ }
144
+
145
+ .spinner:not(.splash) {
146
+ position: relative;
147
+ &::before {
148
+ content: '';
149
+ box-sizing: border-box;
150
+ position: absolute;
151
+ border-radius: 50%;
152
+ z-index: kit.z('spinner');
153
+ border: 2px solid var(--spinner-color);
154
+ border-top-color: var(--spinner-side-color);
155
+ animation: spinner 0.6s linear infinite;
156
+ top: 50%;
157
+ left: 50%;
158
+ // Size from the single-source token, centred via negative
159
+ // half-margins. Applies to any host element — div, section, aside,
160
+ // ul — rather than a fixed list of them.
161
+ width: var(--spinner-size);
162
+ height: var(--spinner-size);
163
+ margin-top: calc(var(--spinner-size) / -2);
164
+ margin-left: calc(var(--spinner-size) / -2);
165
+ }
166
+ }
167
+
168
+ // Full-page spinner: pin to the viewport centre rather than the host element.
169
+ // Size and centring come from the base rule above. body::before belongs to
170
+ // this spinner alone — the page vignette hangs off html::before precisely so
171
+ // the two can never share one pseudo-element (page-vignette in _mixins.scss).
172
+ body:not(.splash).spinner::before {
173
+ position: fixed;
174
+ }
package/_mixins.scss ADDED
@@ -0,0 +1,138 @@
1
+ @use '@greendrake/scss-kit' as kit;
2
+
3
+ $max-content-width: 850px;
4
+
5
+ // Applies the $light / $dark custom-property maps under the .light/.dark :root
6
+ // classes, falling back to prefers-color-scheme when neither class is set.
7
+ @mixin light-dark($light, $dark) {
8
+ :root.light {
9
+ @include kit.apply-props($light);
10
+ }
11
+
12
+ @media screen and (prefers-color-scheme: light) {
13
+ :root:not(.dark) {
14
+ @include kit.apply-props($light);
15
+ }
16
+ }
17
+
18
+ :root.dark {
19
+ @include kit.apply-props($dark);
20
+ }
21
+
22
+ @media screen and (prefers-color-scheme: dark) {
23
+ :root:not(.light) {
24
+ @include kit.apply-props($dark);
25
+ }
26
+ }
27
+ }
28
+
29
+ // Page-canvas vignette: a flat tone at the centre falling off to a darker
30
+ // edge tone, painted with an inset box shadow on a fixed overlay rather than
31
+ // a radial gradient — Firefox's WebRender can mis-render the singular centre
32
+ // pixel of a page-sized radial gradient as a stray bright dot that comes and
33
+ // goes with window moves/resizes; a shadow has no degenerate point. Reads
34
+ // --page-background / --page-vignette-color from the palette.
35
+ //
36
+ // It hangs off <html>, not <body>: an element has exactly one ::before, and
37
+ // body's belongs to the full-page loading spinner (`body.spinner::before`, see
38
+ // _index.scss). Sharing it made the two fight — the spinner ring inherited the
39
+ // vignette's page-sized inset shadow, and the vignette vanished for the
40
+ // duration of every page load. The canvas is html's job anyway; it already
41
+ // carries the page background above.
42
+ @mixin page-vignette {
43
+ html {
44
+ // Also covers the overscroll gutter, which no fixed layer reaches.
45
+ background-color: var(--page-background);
46
+ &::before {
47
+ content: '';
48
+ position: fixed;
49
+ inset: 0;
50
+ z-index: -1;
51
+ pointer-events: none;
52
+ @include page-canvas;
53
+ }
54
+ }
55
+ }
56
+
57
+ // The canvas as a paintable layer: flat tone plus vignette. Anything that must
58
+ // READ as the page rather than as a panel paints this over a viewport-anchored
59
+ // box (`position: fixed; inset: 0`) and then masks itself down to the part it
60
+ // needs — page-scrim below is that construction. Same construction over the
61
+ // same box as the canvas above, so the painted region matches the page pixel
62
+ // for pixel, vignette falloff included, and its edges are the mask's business
63
+ // alone.
64
+ // The blur is sized against the SHORT viewport side, and modestly: a radius
65
+ // approaching the box's own width leaves the falloffs from opposite edges
66
+ // overlapping everywhere, so the gradient washes out into a flat tone — and
67
+ // engines diverge sharply in that saturated regime (WebKit floods the layer
68
+ // with the shadow colour outright, which is what a phone shows). 30vmin keeps
69
+ // every viewport shape well inside the range Chromium and WebKit rasterise
70
+ // identically.
71
+ @mixin page-canvas {
72
+ background-color: var(--page-background);
73
+ box-shadow: inset 0 0 30vmin var(--page-vignette-color);
74
+ }
75
+
76
+ // The backing for a bar stuck over scrolling content — a sticky header or
77
+ // footer — that must not read as a panel bolted onto the page. It is not a
78
+ // colour of its own but a second copy of the page canvas on its own
79
+ // viewport-anchored box, masked down to the strip the bar occupies: where it is
80
+ // opaque it is indistinguishable from the page behind it, vignette falloff
81
+ // included. Sits behind the bar's own content (z-index -1 inside the stacking
82
+ // context the bar's z-index opens), so nothing about the bar's children moves.
83
+ //
84
+ // $edge is the viewport edge the bar is stuck to. The mask holds full opacity
85
+ // for $solid from that edge — the band the bar's content occupies — then takes
86
+ // $ramp to reach clear, so content scrolls into view behind the bar with no
87
+ // edge to see.
88
+ @mixin page-scrim($edge, $solid, $ramp) {
89
+ @if $edge != top and $edge != bottom {
90
+ @error 'page-scrim: $edge must be top or bottom, got #{$edge}';
91
+ }
92
+ // The gradient runs from the far edge toward $edge, so its opaque stop
93
+ // lands on the bar.
94
+ $away: top;
95
+ @if $edge == top {
96
+ $away: bottom;
97
+ }
98
+ &::before {
99
+ content: '';
100
+ position: fixed;
101
+ inset: 0;
102
+ z-index: -1;
103
+ pointer-events: none;
104
+ @include page-canvas;
105
+ mask-image: linear-gradient(to #{$away}, #000 #{$solid}, transparent calc(#{$solid} + #{$ramp}));
106
+ }
107
+ }
108
+
109
+ @mixin button-colors {
110
+ background: var(--button-background);
111
+ &:hover {
112
+ filter: brightness(120%);
113
+ }
114
+ }
115
+
116
+ @mixin wider-than-max-content {
117
+ @media (min-width: $max-content-width) {
118
+ @content;
119
+ }
120
+ }
121
+
122
+ @mixin overlay-icon-button($size: 24px) {
123
+ position: absolute;
124
+ width: $size;
125
+ height: $size;
126
+ border-radius: var(--border-radius);
127
+ background: var(--overlay-button-bg);
128
+ opacity: 0.5;
129
+ color: #fff;
130
+ border: 0;
131
+ padding: 0;
132
+ cursor: pointer;
133
+ line-height: $size;
134
+ font-size: calc($size * 0.55);
135
+ &:hover {
136
+ opacity: 1;
137
+ }
138
+ }
@@ -0,0 +1,44 @@
1
+ // Default dashboard palette: opt-in via `@use '@greendrake/theme/dashboard-palette';`.
2
+ @use '@greendrake/scss-kit' as kit;
3
+ @use 'mixins' as *;
4
+
5
+ @include light-dark(
6
+ (
7
+ --font-color: #222,
8
+ --background-color: #ddd,
9
+ --form-background: #f5f5f5,
10
+ --form-background-hover: #fff,
11
+ --border-color: #555,
12
+ --border-color-light: #999,
13
+ --border-color-hover: #000,
14
+ --link-color-hover: #000,
15
+ --light-switch: kit.icon-svg('moonstar'),
16
+ --spinner-color: #ccc,
17
+ --page-background: #e6e6e6,
18
+ --page-vignette-color: #cdcdcd
19
+ ),
20
+ (
21
+ --font-color: #eee,
22
+ --background-color: #222,
23
+ --form-background: #222,
24
+ --form-background-hover: #000,
25
+ --border-color: #aaa,
26
+ --border-color-light: #666,
27
+ --border-color-hover: #fff,
28
+ --link-color-hover: #fff,
29
+ --light-switch: kit.icon-svg('sun'),
30
+ --spinner-color: #ccc,
31
+ --page-background: #202020,
32
+ --page-vignette-color: #0c0c0c
33
+ )
34
+ );
35
+
36
+ // Modal chrome vars consumed by @greendrake/ui's Modal — part of the default
37
+ // dashboard look (the dark-glass window over a dimmed backdrop).
38
+ :root {
39
+ --modal-background: var(--background-color);
40
+ --modal-box-shadow: rgba(255, 255, 255, 0.5) 0px 4px 25px;
41
+ --modal-border-radius: var(--border-radius);
42
+ --modal-backdrop-filter: brightness(0.4);
43
+ --modal-window-backdrop-filter: blur(8px);
44
+ }
package/dashboard.scss ADDED
@@ -0,0 +1,63 @@
1
+ // The full dashboard-app preset: palette + native-control skin + editor-row
2
+ // layout + the shell styling a dashboard would otherwise write for itself.
3
+ // Opt in via
4
+ // `@use '@greendrake/theme/dashboard';` (after `@use '@greendrake/theme'`).
5
+ @use 'dashboard-palette';
6
+ @use 'form-controls';
7
+ @use 'editor-rows';
8
+ @use 'mixins' as *;
9
+
10
+ @include page-vignette;
11
+
12
+ :root {
13
+ --border-radius: 4px;
14
+ --icon-size: 18px;
15
+ // Denser base than the document default: dashboards are dense,
16
+ // information-first surfaces.
17
+ --font-md: 14px;
18
+ // Required by the ui .spinner ring contract (see README).
19
+ --spinner-side-color: var(--form-background);
20
+ }
21
+
22
+ html,
23
+ body {
24
+ height: 100vh;
25
+ overflow: hidden;
26
+ }
27
+
28
+ body {
29
+ padding: 0;
30
+ margin: 0;
31
+ }
32
+
33
+ a:hover {
34
+ color: var(--link-color-hover);
35
+ }
36
+
37
+ label {
38
+ font-size: var(--font-xs);
39
+ font-weight: 500;
40
+ opacity: 0.7;
41
+ }
42
+
43
+ // Headings read as panel/section dividers rather than page titles — every
44
+ // dashboard panel opens with one, and detail views nest h2/h3 under it.
45
+ h1,
46
+ h2,
47
+ h3 {
48
+ font-weight: 500;
49
+ }
50
+
51
+ h1 {
52
+ font-size: var(--font-2xl);
53
+ line-height: 36px;
54
+ margin: var(--space-5) 0 var(--space-3);
55
+ }
56
+
57
+ h2 {
58
+ border-bottom: 1px solid var(--border-color-light);
59
+ margin: var(--space-4) 0 var(--space-2);
60
+ opacity: 0.8;
61
+ font-size: var(--font-xl);
62
+ line-height: 28px;
63
+ }
@@ -0,0 +1,27 @@
1
+ // Opt-in record-editor/detail-pane layout: `@use '@greendrake/theme/editor-rows';`
2
+ // (the dashboard preset pulls it in). `.EditorRows` is the padded scrolling
3
+ // pane, `.EditorRows__row` a label/value line. The classes are independent:
4
+ // a component whose root layout diverges may adopt the rows alone.
5
+
6
+ .EditorRows {
7
+ padding: var(--space-4);
8
+ overflow: auto;
9
+ height: 100%;
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ .EditorRows__row {
14
+ display: flex;
15
+ gap: var(--space-3);
16
+ padding: var(--space-2) 0;
17
+ border-bottom: 1px solid var(--border-color-light);
18
+ > label {
19
+ min-width: 160px;
20
+ font-size: var(--font-xs);
21
+ opacity: 0.7;
22
+ padding-top: 2px;
23
+ }
24
+ > div {
25
+ flex-grow: 1;
26
+ }
27
+ }
@@ -0,0 +1,48 @@
1
+ // Opt-in native form-control skin: `@use '@greendrake/theme/form-controls';`.
2
+ // The block a dashboard would otherwise write into its own main.scss.
3
+ // Relies on the palette vars (--form-background, --border-color*, --font-color)
4
+ // plus --border-radius, which stays app-supplied (see README).
5
+
6
+ button,
7
+ input,
8
+ select,
9
+ textarea {
10
+ border-radius: var(--border-radius);
11
+ color: var(--font-color);
12
+ background: var(--form-background);
13
+ border: 1px solid var(--border-color);
14
+ }
15
+
16
+ button {
17
+ cursor: pointer;
18
+ padding: var(--space-1) var(--space-4);
19
+ &:hover:not(:disabled) {
20
+ border-color: var(--border-color-hover);
21
+ background-color: var(--form-background-hover);
22
+ }
23
+ &:disabled {
24
+ opacity: 0.5;
25
+ cursor: default;
26
+ }
27
+ }
28
+
29
+ input,
30
+ select,
31
+ textarea {
32
+ padding: var(--space-1) var(--space-2);
33
+ &:focus {
34
+ outline: none;
35
+ border-color: var(--border-color-hover);
36
+ background-color: var(--form-background-hover);
37
+ }
38
+ }
39
+
40
+ // Small in-control variant of the theme's .spinner ring (action buttons while
41
+ // their async action is in flight): the sole size knob is --spinner-size, so the
42
+ // ring geometry stays owned by the base rule.
43
+ button.spinner {
44
+ pointer-events: none;
45
+ cursor: default;
46
+ color: transparent !important;
47
+ --spinner-size: 24px;
48
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@greendrake/theme",
3
+ "version": "0.1.0",
4
+ "description": "Design tokens, document-level element styling, a light/dark theming mixin and an opt-in dashboard palette",
5
+ "license": "MIT",
6
+ "author": "Eugene Greendrake <eugene@greendrake.nz>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/greendrake/lib.git",
10
+ "directory": "theme"
11
+ },
12
+ "homepage": "https://github.com/greendrake/lib/tree/main/theme#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/greendrake/lib/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "_index.scss",
18
+ "exports": {
19
+ ".": {
20
+ "sass": "./_index.scss",
21
+ "default": "./_index.scss"
22
+ },
23
+ "./mixins": {
24
+ "sass": "./_mixins.scss",
25
+ "default": "./_mixins.scss"
26
+ },
27
+ "./dashboard-palette": {
28
+ "sass": "./dashboard-palette.scss",
29
+ "default": "./dashboard-palette.scss"
30
+ },
31
+ "./form-controls": {
32
+ "sass": "./form-controls.scss",
33
+ "default": "./form-controls.scss"
34
+ },
35
+ "./editor-rows": {
36
+ "sass": "./editor-rows.scss",
37
+ "default": "./editor-rows.scss"
38
+ },
39
+ "./dashboard": {
40
+ "sass": "./dashboard.scss",
41
+ "default": "./dashboard.scss"
42
+ }
43
+ },
44
+ "files": [
45
+ "_index.scss",
46
+ "_mixins.scss",
47
+ "dashboard-palette.scss",
48
+ "form-controls.scss",
49
+ "editor-rows.scss",
50
+ "dashboard.scss"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "prettier": "@greendrake/dev-config/prettier",
56
+ "dependencies": {
57
+ "@greendrake/css-reset": "0.1.0",
58
+ "@greendrake/scss-kit": "0.1.0"
59
+ },
60
+ "devDependencies": {
61
+ "@greendrake/dev-config": "0.1.0"
62
+ }
63
+ }