@humanforest/ui 0.1.2 → 0.1.3
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/package.json +8 -4
- package/preset.gen.js +1424 -0
- package/src/contexts/marketing-theme.ts +1 -1
- package/src/filter/FFilterChip.vue +24 -6
- package/src/kpi/FKpi.vue +18 -17
- package/src/kpi/FKpiGroup.vue +15 -7
- package/src/kpi/kpi.theme.ts +12 -4
- package/src/kpi/kpiFormat.ts +14 -0
- package/src/logo/FSubLogo.vue +9 -7
- package/src/map/FVehicleMark.vue +6 -12
- package/src/themes/button.mechanics.ts +27 -17
- package/src/themes/button.ts +0 -2
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* Button: keycap voice — rounder (lg), 2px edge, deeper lip with a bigger hover lift (rest −3px,
|
|
24
24
|
* hover −5px; web md is −2/−3). Labels stay GT Haptik: Mohr is for headlines — the italic-Mohr
|
|
25
25
|
* button label is the MOBILE voice, not marketing's. The base classes below re-voice the
|
|
26
|
-
* edge/lip-bearing rungs (keycap, pad, framed,
|
|
26
|
+
* edge/lip-bearing rungs (keycap, pad, framed, subtle, outline — they read --btn-edge /
|
|
27
27
|
* --btn-y*); the flat rungs (solid, soft, ghost, link) read neither and share the restrained web
|
|
28
28
|
* rendering — bold delivery rides on the keycap/lg prop default (CONTEXTS-SPEC.md §6).
|
|
29
29
|
*
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// where the is/is-any-of promotion lives.
|
|
11
11
|
import { computed, onMounted, ref, shallowRef } from 'vue';
|
|
12
12
|
import { CalendarDate } from '@internationalized/date';
|
|
13
|
+
import type { DateRange, DateValue } from 'reka-ui';
|
|
13
14
|
import { filterTheme, type FilterSlot } from './filter.theme';
|
|
14
15
|
import { OPERATOR_LABEL, operatorsFor, type FilterSchema, type Operator } from './filterSchema';
|
|
15
16
|
import { isComplete, summariseClause, type Clause, type RelativeUnit } from './filterClause';
|
|
@@ -149,18 +150,35 @@ const onDateOpen = (open: boolean) => {
|
|
|
149
150
|
if (open) dateKey.value += 1;
|
|
150
151
|
};
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
/*
|
|
154
|
+
⚠ THESE HANDLERS TAKE WHAT UCalendar EMITS, NOT WHAT THIS COMPONENT WANTS. They used to be typed
|
|
155
|
+
as the narrow shape the body reads — `{ start?: CalendarDate; end?: CalendarDate }` — which is a
|
|
156
|
+
SUBSET of the emitted `DateRange | null`, and a handler cannot be narrower than its emit. It type-
|
|
157
|
+
checked in this repo (nothing here typechecks packages/ui) and failed in every consumer's own
|
|
158
|
+
`vue-tsc`, against source they cannot edit.
|
|
159
|
+
|
|
160
|
+
So the parameter is the emitted type and the narrowing is explicit. `instanceof` rather than a
|
|
161
|
+
cast: the calendar can hand back a CalendarDateTime or a ZonedDateTime, and calendarToIso is only
|
|
162
|
+
correct for a plain date.
|
|
163
|
+
*/
|
|
164
|
+
const asCalendarDate = (value: unknown): CalendarDate | null =>
|
|
165
|
+
value instanceof CalendarDate ? value : null;
|
|
166
|
+
|
|
167
|
+
const onRangePicked = (range: DateRange | null | undefined) => {
|
|
168
|
+
const start = asCalendarDate(range?.start);
|
|
169
|
+
const end = asCalendarDate(range?.end);
|
|
170
|
+
if (!start || !end) return;
|
|
154
171
|
emit('update', {
|
|
155
|
-
value: [calendarToIso(
|
|
172
|
+
value: [calendarToIso(start), calendarToIso(end)] as [string | null, string | null],
|
|
156
173
|
});
|
|
157
174
|
dateOpen.value = false;
|
|
158
175
|
};
|
|
159
176
|
|
|
160
177
|
/** A single date is complete the moment it is picked, so the calendar closes behind it. */
|
|
161
|
-
const onDatePicked = (picked:
|
|
162
|
-
|
|
163
|
-
|
|
178
|
+
const onDatePicked = (picked: DateValue | DateValue[] | DateRange | null | undefined) => {
|
|
179
|
+
const date = asCalendarDate(picked);
|
|
180
|
+
if (!date) return;
|
|
181
|
+
emit('update', { value: calendarToIso(date) });
|
|
164
182
|
dateOpen.value = false;
|
|
165
183
|
};
|
|
166
184
|
|
package/src/kpi/FKpi.vue
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// registers itself, spans four subgrid rows, and may become a tab or a toggle.
|
|
10
10
|
import { computed, inject, onBeforeUnmount, onMounted, useId, useSlots } from 'vue';
|
|
11
11
|
import { kpiTheme, KPI_GROUP_KEY, type KpiSlot } from './kpi.theme';
|
|
12
|
-
import { formatMetric, type KpiFormat } from './kpiFormat';
|
|
12
|
+
import { formatMetric, joinCaption, type KpiFormat } from './kpiFormat';
|
|
13
13
|
import { resolveDelta, type KpiDelta, type KpiSentiment } from './kpiDelta';
|
|
14
14
|
|
|
15
15
|
const props = withDefaults(
|
|
@@ -117,16 +117,6 @@ const delta = computed(() =>
|
|
|
117
117
|
}),
|
|
118
118
|
);
|
|
119
119
|
|
|
120
|
-
// Period first, then the baseline it was measured against — "last 7 days · vs previous 7 days".
|
|
121
|
-
// The other order reads as one repeated phrase.
|
|
122
|
-
const captionParts = computed(() =>
|
|
123
|
-
[
|
|
124
|
-
props.period,
|
|
125
|
-
props.delta ? `vs ${props.delta.comparisonLabel}` : undefined,
|
|
126
|
-
props.partial ? 'Period in progress' : undefined,
|
|
127
|
-
].filter(Boolean),
|
|
128
|
-
);
|
|
129
|
-
|
|
130
120
|
const asOfText = computed(() => {
|
|
131
121
|
if (!props.asOf) return undefined;
|
|
132
122
|
const d = props.asOf instanceof Date ? props.asOf : new Date(props.asOf);
|
|
@@ -135,6 +125,21 @@ const asOfText = computed(() => {
|
|
|
135
125
|
: `Updated ${new Intl.DateTimeFormat(props.locale ?? 'en-GB', { timeStyle: 'short' }).format(d)}`;
|
|
136
126
|
});
|
|
137
127
|
|
|
128
|
+
// Period first, then the baseline it was measured against — "last 7 days · vs previous 7 days".
|
|
129
|
+
// The other order reads as one repeated phrase. `asOf` joins the SAME run so it gets a separator for
|
|
130
|
+
// free; the caveat is the only part kept as its own element, because aria-describedby needs an id to
|
|
131
|
+
// point at and a run of text cannot carry one.
|
|
132
|
+
const captionLead = computed(() =>
|
|
133
|
+
isBlank.value && props.reason
|
|
134
|
+
? REASON_COPY[props.reason]
|
|
135
|
+
: joinCaption(
|
|
136
|
+
props.period,
|
|
137
|
+
props.delta && `vs ${props.delta.comparisonLabel}`,
|
|
138
|
+
props.partial && 'Period in progress',
|
|
139
|
+
asOfText.value,
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
|
|
138
143
|
const caveatId = computed(() => (props.caveat ? `${uid}-caveat` : undefined));
|
|
139
144
|
|
|
140
145
|
const styles = computed(() =>
|
|
@@ -235,12 +240,8 @@ const onKeydown = (e: KeyboardEvent) => {
|
|
|
235
240
|
</dd>
|
|
236
241
|
|
|
237
242
|
<p :class="cls('caption')">
|
|
238
|
-
<slot name="caption">
|
|
239
|
-
|
|
240
|
-
<span v-else>{{ captionParts.join(' · ') }}</span>
|
|
241
|
-
</slot>
|
|
242
|
-
<span v-if="asOfText" class="ms-1">{{ asOfText }}</span>
|
|
243
|
-
<span v-if="caveat" :id="caveatId" class="ms-1">{{ caveat }}</span>
|
|
243
|
+
<slot name="caption">{{ captionLead }}</slot>
|
|
244
|
+
<span v-if="caveat" :id="caveatId">{{ captionLead ? ' · ' : '' }}{{ caveat }}</span>
|
|
244
245
|
</p>
|
|
245
246
|
|
|
246
247
|
<div v-if="$slots.visual" :class="cls('visual')"><slot name="visual" /></div>
|
package/src/kpi/FKpiGroup.vue
CHANGED
|
@@ -133,7 +133,7 @@ const gridStyle = computed(() =>
|
|
|
133
133
|
:aria-orientation="selectable && !multiple ? orientation : undefined"
|
|
134
134
|
class="grid grid-rows-[auto_auto_auto_auto] items-stretch"
|
|
135
135
|
:class="[
|
|
136
|
-
divided ? 'gap-px
|
|
136
|
+
divided ? 'gap-px' : 'gap-4',
|
|
137
137
|
// Ring outset, not inset: `divided` rules are this element's background through 1px gaps,
|
|
138
138
|
// so the tiles sit flush to its border box and an inset stroke lands on them.
|
|
139
139
|
divided && !flush && 'overflow-hidden rounded-lg ring-1 ring-default',
|
|
@@ -143,13 +143,21 @@ const gridStyle = computed(() =>
|
|
|
143
143
|
>
|
|
144
144
|
<!-- The dl is display:contents so the tiles join the grid directly, which also means it is
|
|
145
145
|
the group's ONLY element child — a `divide-x` on the grid would target the dl and never
|
|
146
|
-
reach a tile.
|
|
147
|
-
|
|
148
|
-
rule would draw a stray edge on the first tile of every wrapped row.
|
|
146
|
+
reach a tile. A `divide-*` rule is wrong for a second reason too: it would draw a stray
|
|
147
|
+
edge on the first tile of every wrapped row.
|
|
149
148
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
the
|
|
149
|
+
★★ THE HAIRLINES ARE THE TILES' OWN RINGS MEETING IN THE 1px GAP, not this element's
|
|
150
|
+
background showing through. They were the background until a four-tile group on three
|
|
151
|
+
columns painted the two leftover cells as a filled grey slab — a ~460x130px rectangle at
|
|
152
|
+
every width from 768 to 1100px, iPad landscape included. A background cannot tell a seam
|
|
153
|
+
from an empty cell; a ring on the tile only exists where a tile does.
|
|
154
|
+
|
|
155
|
+
Tailwind's ring is an OUTSET box-shadow, so two neighbours each cast 1px into the same
|
|
156
|
+
1px gap and the seam is the same ink and the same geometry as before — `ring-default` is
|
|
157
|
+
--ui-border, which is what this comment used to insist the colour had to be: the gaps read
|
|
158
|
+
as the same rules as the ring around the row, where `bg-accented` sits several steps
|
|
159
|
+
lighter and came out brighter than the border enclosing it. `overflow-hidden` clips the
|
|
160
|
+
outer tiles' rings, so the perimeter stays the group's own single ring and never doubles. -->
|
|
153
161
|
<dl class="contents">
|
|
154
162
|
<slot />
|
|
155
163
|
</dl>
|
package/src/kpi/kpi.theme.ts
CHANGED
|
@@ -54,10 +54,14 @@ export const kpiTheme = tv({
|
|
|
54
54
|
// radius. Keeping the radius would round each segment's fill inside a joined strip and leave
|
|
55
55
|
// the selected tile's underline looking detached at both ends; the group's own container
|
|
56
56
|
// supplies the outer corners.
|
|
57
|
-
// The tile stays OPAQUE
|
|
58
|
-
//
|
|
57
|
+
// The tile stays OPAQUE, and it draws its OWN seam: `ring-1 ring-default` is an outset
|
|
58
|
+
// box-shadow, so two neighbours each cast 1px into the group's 1px gap and meet as one hairline.
|
|
59
|
+
// The group used to paint --ui-border edge to edge and let the gaps show it through, which drew
|
|
60
|
+
// the same seam but could not tell a seam from an empty grid cell — four tiles on three columns
|
|
61
|
+
// left the two leftovers as a filled grey slab. Ink that lives on the tile exists only where a
|
|
62
|
+
// tile does. See FKpiGroup.vue.
|
|
59
63
|
naked: {
|
|
60
|
-
true: { root: 'forest-kpi-joined bg-default shadow-none ring-
|
|
64
|
+
true: { root: 'forest-kpi-joined bg-default shadow-none ring-1 ring-default' },
|
|
61
65
|
},
|
|
62
66
|
/**
|
|
63
67
|
* How the `visual` slot meets the card. Two graphics, opposite needs, and the slot used to
|
|
@@ -133,7 +137,11 @@ export const kpiTheme = tv({
|
|
|
133
137
|
naked: true,
|
|
134
138
|
selected: true,
|
|
135
139
|
class: {
|
|
136
|
-
|
|
140
|
+
// ⚠ `ring-1 ring-default` AND NOT `ring-0`: the seam now lives on the tile, so cancelling
|
|
141
|
+
// the ring outright would punch a gap in the strip's hairlines around the selected segment.
|
|
142
|
+
// Both halves are needed — a bare `ring-1` loses the width fight but keeps `selected`'s
|
|
143
|
+
// primary colour, leaving a coloured seam.
|
|
144
|
+
root: 'ring-1 ring-default bg-elevated after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:bg-primary',
|
|
137
145
|
},
|
|
138
146
|
},
|
|
139
147
|
// Surface-painting reaches for the tinted card variants. There is no red accent, so a critical
|
package/src/kpi/kpiFormat.ts
CHANGED
|
@@ -70,3 +70,17 @@ export const formatPercent = (
|
|
|
70
70
|
minimumFractionDigits: 0,
|
|
71
71
|
maximumFractionDigits: precision,
|
|
72
72
|
}).format(fraction);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The caption's parts in reading order, joined by the tile's separator.
|
|
76
|
+
*
|
|
77
|
+
* ★★ EVERY PART GOES THROUGH HERE, which is the point. FKpi used to join three parts with ' · ' and
|
|
78
|
+
* then append `asOf` and `caveat` as bare sibling spans carrying only a 4px margin — so a tile given
|
|
79
|
+
* both a comparison label and a caveat rendered "vs previous quarterVerified with our partners", with
|
|
80
|
+
* no separator at all. The margin was never the fix: Vue's whitespace condense emits no text node
|
|
81
|
+
* between sibling spans, so `textContent` ran the words together and assistive tech read one word.
|
|
82
|
+
*
|
|
83
|
+
* Falsy parts drop out, so a tile carrying one part gets no stray leading or trailing dot.
|
|
84
|
+
*/
|
|
85
|
+
export const joinCaption = (...parts: (string | false | null | undefined)[]): string =>
|
|
86
|
+
parts.filter((p): p is string => !!p).join(' · ');
|
package/src/logo/FSubLogo.vue
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Variants: lockup (lead + keycap) · badge (keycap alone) · icon (square, first letter) · joined
|
|
11
11
|
// (lead + sub-brand butted on as one word).
|
|
12
|
-
import { computed } from 'vue';
|
|
12
|
+
import { computed, useId } from 'vue';
|
|
13
13
|
import { WORDMARK_PATHS, WORDMARK_W, WORDMARK_H } from '@humanforest/tokens/logo';
|
|
14
14
|
import { setWord, runInk } from '@humanforest/tokens/glyphs';
|
|
15
15
|
import {
|
|
@@ -91,7 +91,14 @@ const rect = computed(() => {
|
|
|
91
91
|
|
|
92
92
|
// One clip per instance trims the flat ascenders. The id must be unique per mark — two lockups on a
|
|
93
93
|
// page would otherwise share a def, and a downloaded file has to carry its own.
|
|
94
|
-
|
|
94
|
+
//
|
|
95
|
+
// ★★ useId(), NOT A MODULE COUNTER. Module state lives for the whole SSR process, so request N emits
|
|
96
|
+
// `fsub-cut-N` while a freshly hydrating client always starts at 1. The server's markup is
|
|
97
|
+
// internally consistent, so the mark looks right and only a hydration warning fires — but the client
|
|
98
|
+
// vnode holds an id the document does not, and the first `variant` change to mount a new glyph group
|
|
99
|
+
// points it at `url(#fsub-cut-1)`. That def does not exist, the ascender trim silently stops, and
|
|
100
|
+
// GT's flat ascenders come back — which is the entire reason this clip exists.
|
|
101
|
+
const clipId = `fsub-cut-${useId()}`;
|
|
95
102
|
// the trim line: the t's ink top, which is where the drawn f stops
|
|
96
103
|
const CUT_Y = setWord('t', { font: 'gt', size: SUBLOGO_GT_SIZE, baseline: SUBLOGO_BASELINE }).cutY;
|
|
97
104
|
|
|
@@ -127,11 +134,6 @@ const vars = computed(() => ({
|
|
|
127
134
|
const label = computed(() => `${props.lead} ${word.value}`);
|
|
128
135
|
</script>
|
|
129
136
|
|
|
130
|
-
<script lang="ts">
|
|
131
|
-
// module-scoped so every instance on a page gets its own clip id
|
|
132
|
-
let uid = 0;
|
|
133
|
-
</script>
|
|
134
|
-
|
|
135
137
|
<template>
|
|
136
138
|
<svg
|
|
137
139
|
class="fsub" :viewBox="viewBox" :style="vars"
|
package/src/map/FVehicleMark.vue
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* ⚠ MODULE SCOPE, deliberately. `<script setup>` runs once PER INSTANCE, so a counter declared there resets
|
|
4
|
-
* to the same value for every mark — every instance would take the id `fvm1`, and the gradient collision this
|
|
5
|
-
* prefix exists to prevent would be back with extra steps.
|
|
6
|
-
*/
|
|
7
|
-
let seq = 0;
|
|
8
|
-
const nextUid = () => `fvm${(seq += 1)}`;
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
1
|
<script setup lang="ts">
|
|
12
2
|
// FVehicleMark — one vehicle, drawn from its facts. NOT a map component.
|
|
13
3
|
//
|
|
@@ -37,7 +27,7 @@ const nextUid = () => `fvm${(seq += 1)}`;
|
|
|
37
27
|
// <FMarker :lng-lat="v.at" selected interactive><FVehicleMark v-bind="v.facts" /></FMarker>
|
|
38
28
|
// Usage anywhere else:
|
|
39
29
|
// <FVehicleMark state="FUNCTIONAL" battery="LOW" :size="28" />
|
|
40
|
-
import { computed } from 'vue';
|
|
30
|
+
import { computed, useId } from 'vue';
|
|
41
31
|
import { MARKER_SVG } from './markerAsset';
|
|
42
32
|
import manifest from '../../icons/forest/map/manifest.json';
|
|
43
33
|
import {
|
|
@@ -114,7 +104,11 @@ const colour = (t: string) => resolveMapColor(`var(${t})`);
|
|
|
114
104
|
* icon is its own document, and broken here: two of these on one page would both resolve `url(#inUse)` to the
|
|
115
105
|
* first one's stops, so a list of vehicles would show the first row's gradient on every row.
|
|
116
106
|
*/
|
|
117
|
-
|
|
107
|
+
// ★★ useId() AND NOT A MODULE COUNTER. A module-scope counter does give every instance a distinct
|
|
108
|
+
// id, which is what this needs — but it never resets under SSR, so the server writes `fvm7` into the
|
|
109
|
+
// v-html'd asset where the hydrating client generates `fvm1`. useId() is stable across the pair and
|
|
110
|
+
// still unique per instance, which is the whole requirement.
|
|
111
|
+
const uid = useId();
|
|
118
112
|
|
|
119
113
|
const artwork = computed(() => {
|
|
120
114
|
void tokenMode.value;
|
|
@@ -98,22 +98,29 @@ export const SOLID = [
|
|
|
98
98
|
FOCUS,
|
|
99
99
|
].join(' ');
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
/*
|
|
102
|
+
GLOSS IS GONE, AND IT IS WORTH KNOWING WHY BEFORE BRINGING IT BACK.
|
|
103
|
+
|
|
104
|
+
It was a web-only emphasis: framed's lip outline with a line of light one pixel inside the top,
|
|
105
|
+
drawn as the face's OWN colour lightened in-gamut —
|
|
106
|
+
oklch(from var(--btn-face) calc(l + var(--btn-shine-l,0.25)) c h)
|
|
107
|
+
— so the sheen stayed on-brand per intent and worked in both modes. Switched off in d6e99218
|
|
108
|
+
(2026-07-08) and never switched back, which left two faults standing:
|
|
109
|
+
|
|
110
|
+
· `variant: { gloss: '' }` stayed in the enum with no compound to style it, so
|
|
111
|
+
<UButton variant="gloss"> type-checked, rendered, and looked like nothing at all.
|
|
112
|
+
· it was the ONLY relative-colour construct in the shipped surface, and postcss-calc's Jison
|
|
113
|
+
grammar has no term for a bare channel keyword — so every consumer's production build logged
|
|
114
|
+
Lexical error on line 1: Unrecognized text … l + var(--btn-shine-l,.25)
|
|
115
|
+
The declaration survived into the CSS, so it was cosmetic; it also fails any build treating
|
|
116
|
+
warnings as errors, and it fired for everyone.
|
|
117
|
+
|
|
118
|
+
REVIVING IT: the arithmetic is load-bearing and must stay channel arithmetic — `color-mix(… white)`
|
|
119
|
+
drags chroma to zero and desaturates the line. To keep the warning away, write the sum as
|
|
120
|
+
`max(l + var(--btn-shine-l,0.25), 0)`: measured identical to the last decimal in Chromium across a
|
|
121
|
+
dark face, neutral-900 at shine 0.5 and neutral-50, because L can never go under 0 — it simply
|
|
122
|
+
drops the `calc` token the lexer chokes on.
|
|
123
|
+
*/
|
|
117
124
|
|
|
118
125
|
export const SUBTLE = [
|
|
119
126
|
// A ramp-pinned INSET RING (a box-shadow, not a border → no added height) over a plain surface fill.
|
|
@@ -184,7 +191,10 @@ export const PAL_SECONDARY = '[--btn-face:var(--warm-500)] dark:[--btn-face:var(
|
|
|
184
191
|
// outline hover edge stays monochrome instead of leaking brand green via the shared --btn-ring.
|
|
185
192
|
// Rest --btn-line is 300/700 (not the ramp's 400/600) to match UInput's resting border
|
|
186
193
|
// (ring-accented → --ui-border-accented = neutral-300/700), so a neutral button sits flush with a field.
|
|
187
|
-
|
|
194
|
+
// --btn-shine-l went with GLOSS: neutral's near-black face needed a bigger lift than 0.25 to read,
|
|
195
|
+
// and nothing reads the variable now. It was still emitting two dead utility classes into every
|
|
196
|
+
// consumer's stylesheet. Restore both halves (0.5 light / 0.25 dark) if gloss ever comes back.
|
|
197
|
+
export const PAL_NEUTRAL = '[--btn-face:var(--neutral-900)] dark:[--btn-face:var(--neutral-50)] [--btn-hover:var(--neutral-800)] dark:[--btn-hover:var(--neutral-100)] [--btn-press:var(--neutral-700)] dark:[--btn-press:var(--neutral-200)] [--btn-lip:var(--neutral-400)] dark:[--btn-lip:var(--neutral-300)] [--btn-ring:var(--neutral-500)] [--btn-t1:var(--neutral-50)] dark:[--btn-t1:var(--neutral-950)] [--btn-t2:var(--neutral-100)] dark:[--btn-t2:var(--neutral-900)] [--btn-t3:var(--neutral-200)] dark:[--btn-t3:var(--neutral-800)] [--btn-line:var(--neutral-300)] dark:[--btn-line:var(--neutral-700)] [--btn-text:var(--neutral-900)] dark:[--btn-text:var(--neutral-100)] [--btn-ink:var(--neutral-0)] dark:[--btn-ink:var(--neutral-900)]';
|
|
188
198
|
|
|
189
199
|
// All seven intents share the colour-keyed solid/flat/outline/soft/ghost classes.
|
|
190
200
|
export const ALL = ['primary', 'secondary', 'success', 'info', 'warning', 'error', 'neutral'];
|
package/src/themes/button.ts
CHANGED
|
@@ -70,7 +70,6 @@ export const buttonTheme = {
|
|
|
70
70
|
keycap: '',
|
|
71
71
|
pad: '',
|
|
72
72
|
framed: '',
|
|
73
|
-
gloss: '',
|
|
74
73
|
subtle: '',
|
|
75
74
|
link: '',
|
|
76
75
|
},
|
|
@@ -114,7 +113,6 @@ export const buttonTheme = {
|
|
|
114
113
|
...emphasis('keycap', KEYCAP),
|
|
115
114
|
...emphasis('pad', PAD),
|
|
116
115
|
...emphasis('framed', FRAMED),
|
|
117
|
-
// ...emphasis('gloss', GLOSS),
|
|
118
116
|
...emphasis('solid', SOLID),
|
|
119
117
|
...emphasis('subtle', SUBTLE),
|
|
120
118
|
...emphasis('soft', SOFT),
|