@stonedogcode/style 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +18 -0
- package/README.md +699 -0
- package/package.json +95 -0
- package/src/components/DictationControls.tsx +141 -0
- package/src/components/DictationPrompt.tsx +78 -0
- package/src/components/StyledBox.tsx +174 -0
- package/src/components/StyledButton.tsx +144 -0
- package/src/components/StyledCollapsible.tsx +127 -0
- package/src/components/StyledDefinitionList.tsx +134 -0
- package/src/components/StyledFieldset.tsx +157 -0
- package/src/components/StyledFlex.tsx +13 -0
- package/src/components/StyledFooter.tsx +399 -0
- package/src/components/StyledFormLabel.tsx +141 -0
- package/src/components/StyledGrid.tsx +109 -0
- package/src/components/StyledGridItem.tsx +19 -0
- package/src/components/StyledHStack.tsx +145 -0
- package/src/components/StyledHeading.tsx +79 -0
- package/src/components/StyledHrRule.tsx +33 -0
- package/src/components/StyledIcon.tsx +172 -0
- package/src/components/StyledIconButton.tsx +135 -0
- package/src/components/StyledInputBool.tsx +81 -0
- package/src/components/StyledInputRadio.tsx +141 -0
- package/src/components/StyledInputSelect.tsx +115 -0
- package/src/components/StyledInputSlider.tsx +83 -0
- package/src/components/StyledInputText.tsx +146 -0
- package/src/components/StyledInputTextArea.tsx +119 -0
- package/src/components/StyledInputToggle.tsx +224 -0
- package/src/components/StyledList.tsx +188 -0
- package/src/components/StyledScrollbar.tsx +53 -0
- package/src/components/StyledSearch.tsx +78 -0
- package/src/components/StyledSeparator.tsx +38 -0
- package/src/components/StyledSidebar.tsx +555 -0
- package/src/components/StyledSimpleGrid.tsx +99 -0
- package/src/components/StyledSparkLine.tsx +119 -0
- package/src/components/StyledSpinner.tsx +91 -0
- package/src/components/StyledStack.tsx +62 -0
- package/src/components/StyledText.tsx +99 -0
- package/src/components/StyledTooltip.tsx +398 -0
- package/src/components/StyledVStack.tsx +143 -0
- package/src/components/TitleLogo.tsx +223 -0
- package/src/components/create-icon.tsx +66 -0
- package/src/components/create-intent-button.tsx +134 -0
- package/src/components/dictation.ts +71 -0
- package/src/components/intent-buttons.ts +154 -0
- package/src/config/can-hover.ts +75 -0
- package/src/config/density.ts +138 -0
- package/src/config/font-size.ts +113 -0
- package/src/config/intent-icons.tsx +116 -0
- package/src/config/logger.ts +60 -0
- package/src/config/style-config.tsx +263 -0
- package/src/config/types.ts +137 -0
- package/src/index.ts +259 -0
- package/src/preset/index.ts +243 -0
- package/src/preset/recipes/arrows.ts +29 -0
- package/src/preset/recipes/box.ts +122 -0
- package/src/preset/recipes/button.ts +161 -0
- package/src/preset/recipes/dl-list.ts +109 -0
- package/src/preset/recipes/drawer.ts +125 -0
- package/src/preset/recipes/form.ts +95 -0
- package/src/preset/recipes/icon-button.ts +161 -0
- package/src/preset/recipes/icon.ts +34 -0
- package/src/preset/recipes/input-bool.ts +184 -0
- package/src/preset/recipes/input-dropdown.ts +93 -0
- package/src/preset/recipes/input-radio.ts +158 -0
- package/src/preset/recipes/input-surface.ts +152 -0
- package/src/preset/recipes/input-text.ts +17 -0
- package/src/preset/recipes/list.ts +196 -0
- package/src/preset/recipes/menu.ts +28 -0
- package/src/preset/recipes/separator.ts +89 -0
- package/src/preset/recipes/stack.ts +89 -0
- package/src/preset/recipes/striped.ts +34 -0
- package/src/preset/recipes/text.ts +41 -0
- package/src/preset/recipes/tooltip.ts +77 -0
- package/src/preset/semantic-variables.ts +283 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useMemo } from "react";
|
|
4
|
+
import type { DensityProfile, FontSizeProfile, IconSize, ThemeVariant } from "./types";
|
|
5
|
+
import { resolveDensityStep, type DensityBase, type DensityStep } from "./density";
|
|
6
|
+
import { THEME_VARIANTS } from "./types";
|
|
7
|
+
import { IntentIconProvider, type IntentIcons } from "./intent-icons";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Everything this component library needs to know about the host application.
|
|
11
|
+
*
|
|
12
|
+
* The components were extracted from an app where they read a 461-line Zustand
|
|
13
|
+
* store holding auth, widgets, notes, services and config together. Depending on
|
|
14
|
+
* that store is what made them unshareable, so the seam was drawn at the
|
|
15
|
+
* narrowest possible place: **these two settings are all the styling actually
|
|
16
|
+
* needs.** Anything a component wants beyond them belongs in a prop.
|
|
17
|
+
*
|
|
18
|
+
* Keeping this interface small is a load-bearing constraint, not tidiness. Every
|
|
19
|
+
* field added here is a field a second host must supply before it can render a
|
|
20
|
+
* single button.
|
|
21
|
+
*/
|
|
22
|
+
export interface StyleConfig {
|
|
23
|
+
/**
|
|
24
|
+
* The user's app-wide text size. Drives `StyledText`, and through it nearly
|
|
25
|
+
* all visible text.
|
|
26
|
+
*/
|
|
27
|
+
fontSizeProfile: FontSizeProfile;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The user's app-wide appearance. A component may override it per call site;
|
|
31
|
+
* see `useResolvedVariant` for the precedence rule.
|
|
32
|
+
*/
|
|
33
|
+
variant: ThemeVariant;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The **user's** density preference, relative to the app's baseline.
|
|
37
|
+
*
|
|
38
|
+
* Not cosmetic: at `compact` the intent buttons drop their text label and
|
|
39
|
+
* show only their icon, so this changes what a control *says*, not just its
|
|
40
|
+
* padding. That is why it lives here rather than being left to CSS.
|
|
41
|
+
*/
|
|
42
|
+
density: DensityProfile;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The **application's** density baseline — the rung `density: "normal"` means.
|
|
46
|
+
*
|
|
47
|
+
* HopperGuard runs `spacious`, both Optima products `compact`, RozCards
|
|
48
|
+
* `standard`. See `config/density.ts`: this and `density` are a position and
|
|
49
|
+
* an offset on one scale, not two competing axes.
|
|
50
|
+
*
|
|
51
|
+
* Defaults to `standard`, whose spacing (8px) is exactly the fallback the
|
|
52
|
+
* recipes have always used — so a host that says nothing sees no change.
|
|
53
|
+
*/
|
|
54
|
+
densityBase: DensityBase;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The app-wide default icon size, used by any `StyledIcon` that is not given
|
|
58
|
+
* an explicit `size`.
|
|
59
|
+
*
|
|
60
|
+
* This exists because the alternative is naming a size at every call site,
|
|
61
|
+
* which is how an application ends up with three icon scales and no way to
|
|
62
|
+
* retune any of them. Font size is already host-tunable — every `fontSizeMap`
|
|
63
|
+
* entry is a `var(--font-sizes-*)` reference the host can define — and this is
|
|
64
|
+
* the equivalent seam for icons.
|
|
65
|
+
*
|
|
66
|
+
* The default stays `"2x"`: HopperGuard serves an often-elderly, sometimes
|
|
67
|
+
* cognitively-impaired audience and runs deliberately large, and ~150 of its
|
|
68
|
+
* call sites rely on that default. A host wanting a conventional web scale
|
|
69
|
+
* (a business tool for a general audience) sets `"md"`.
|
|
70
|
+
*/
|
|
71
|
+
iconSize: IconSize;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What a host that supplies nothing gets.
|
|
76
|
+
*
|
|
77
|
+
* Both values are deliberately the middle/safest option rather than the
|
|
78
|
+
* smallest or the flashiest: an unconfigured app should be readable and plain,
|
|
79
|
+
* so that forgetting the provider degrades to "looks unstyled" rather than
|
|
80
|
+
* "unreadable at 0.75rem".
|
|
81
|
+
*/
|
|
82
|
+
export const DEFAULT_STYLE_CONFIG: StyleConfig = {
|
|
83
|
+
fontSizeProfile: "md",
|
|
84
|
+
variant: "solid",
|
|
85
|
+
density: "normal",
|
|
86
|
+
// `standard` + `normal` resolves to 8px, which is the fallback baked into
|
|
87
|
+
// every recipe. An unconfigured host renders exactly as it did before the
|
|
88
|
+
// ladder existed.
|
|
89
|
+
densityBase: "standard",
|
|
90
|
+
// Not the middle of the scale, unlike the two above: this one is pinned to
|
|
91
|
+
// what the originating application already renders. Changing it would be an
|
|
92
|
+
// invisible, app-wide visual change to every existing consumer.
|
|
93
|
+
iconSize: "2x",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const StyleConfigContext = createContext<StyleConfig>(DEFAULT_STYLE_CONFIG);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Every setting optional, and explicitly accepting `undefined`.
|
|
100
|
+
*
|
|
101
|
+
* `Partial<StyleConfig>` alone is not enough under
|
|
102
|
+
* `exactOptionalPropertyTypes`: it makes each key optional but still refuses an
|
|
103
|
+
* explicit `undefined`. Hosts routinely pass one — `variant={user?.variant}` —
|
|
104
|
+
* and rejecting that would push a conditional spread into every call site for
|
|
105
|
+
* no benefit, since the provider already treats absent and undefined the same.
|
|
106
|
+
*/
|
|
107
|
+
type OptionalStyleConfig = {
|
|
108
|
+
[K in keyof StyleConfig]?: StyleConfig[K] | undefined;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export interface StonedogStyleProviderProps extends OptionalStyleConfig {
|
|
112
|
+
children: React.ReactNode;
|
|
113
|
+
/**
|
|
114
|
+
* Which icon to draw for each intent — see `config/intent-icons.tsx`.
|
|
115
|
+
*
|
|
116
|
+
* This is how one `StyledDeleteButton` serves a Font Awesome product and a
|
|
117
|
+
* Lucide one. Optional: an unregistered intent simply renders no icon.
|
|
118
|
+
*/
|
|
119
|
+
icons?: IntentIcons | undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Supplies the styling settings to everything beneath it.
|
|
124
|
+
*
|
|
125
|
+
* Wrap the app once, near the root, and feed it from wherever the host keeps
|
|
126
|
+
* user preferences. HopperGuard renders it inside its existing config provider
|
|
127
|
+
* and passes the two values through from the Zustand store; a host with no such
|
|
128
|
+
* store can pass literals, or omit the provider entirely and take the defaults.
|
|
129
|
+
*
|
|
130
|
+
* ```tsx
|
|
131
|
+
* <StonedogStyleProvider fontSizeProfile={profile} variant={variant}>
|
|
132
|
+
* <App />
|
|
133
|
+
* </StonedogStyleProvider>
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* Props are merged over the defaults individually, so a host that only cares
|
|
137
|
+
* about font size does not have to name a variant it has no opinion on.
|
|
138
|
+
*/
|
|
139
|
+
export function StonedogStyleProvider({
|
|
140
|
+
children,
|
|
141
|
+
fontSizeProfile,
|
|
142
|
+
variant,
|
|
143
|
+
iconSize,
|
|
144
|
+
density,
|
|
145
|
+
densityBase,
|
|
146
|
+
icons,
|
|
147
|
+
}: StonedogStyleProviderProps) {
|
|
148
|
+
const value = useMemo<StyleConfig>(
|
|
149
|
+
() => ({
|
|
150
|
+
fontSizeProfile:
|
|
151
|
+
fontSizeProfile ?? DEFAULT_STYLE_CONFIG.fontSizeProfile,
|
|
152
|
+
variant: variant ?? DEFAULT_STYLE_CONFIG.variant,
|
|
153
|
+
iconSize: iconSize ?? DEFAULT_STYLE_CONFIG.iconSize,
|
|
154
|
+
density: density ?? DEFAULT_STYLE_CONFIG.density,
|
|
155
|
+
densityBase: densityBase ?? DEFAULT_STYLE_CONFIG.densityBase,
|
|
156
|
+
}),
|
|
157
|
+
[fontSizeProfile, variant, iconSize, density, densityBase],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<StyleConfigContext.Provider value={value}>
|
|
162
|
+
{/*
|
|
163
|
+
The icon registry rides along with the style config so a host wires up
|
|
164
|
+
one provider, not two — and so the intent buttons cannot end up inside
|
|
165
|
+
a styled tree with no icons registered.
|
|
166
|
+
*/}
|
|
167
|
+
<IntentIconProvider icons={icons ?? {}}>{children}</IntentIconProvider>
|
|
168
|
+
</StyleConfigContext.Provider>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @deprecated Renamed to `StonedogStyleProvider` (NEH-251).
|
|
174
|
+
*
|
|
175
|
+
* Kept so each consumer can bump its submodule pointer on its own schedule
|
|
176
|
+
* rather than every repo moving in one lockstep sweep. Removed once HopperGuard,
|
|
177
|
+
* optima-filings and optima-cloud-saas have all landed their consumer PRs.
|
|
178
|
+
*/
|
|
179
|
+
export const HopperStyleProvider = StonedogStyleProvider;
|
|
180
|
+
|
|
181
|
+
/** @deprecated Renamed to `StonedogStyleProviderProps` (NEH-251). */
|
|
182
|
+
export type HopperStyleProviderProps = StonedogStyleProviderProps;
|
|
183
|
+
|
|
184
|
+
/** The current styling settings. Safe outside a provider — returns defaults. */
|
|
185
|
+
export function useStyleConfig(): StyleConfig {
|
|
186
|
+
return useContext(StyleConfigContext);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The user's app-wide font-size profile. */
|
|
190
|
+
export function useFontSizeProfile(): FontSizeProfile {
|
|
191
|
+
return useStyleConfig().fontSizeProfile;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The app-wide default icon size. `StyledIcon` uses it when given no `size`. */
|
|
195
|
+
export function useIconSize(): IconSize {
|
|
196
|
+
return useStyleConfig().iconSize;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Resolve a control's appearance: **the caller's, else the user's app-wide
|
|
201
|
+
* setting, else `solid`.**
|
|
202
|
+
*
|
|
203
|
+
* This is three lines, which is exactly why it is shared. In the originating
|
|
204
|
+
* codebase each control picked its own default — a checkbox hard-coded `solid`,
|
|
205
|
+
* a wheel picker defaulted to `outline`, a phone field to `solid` — each
|
|
206
|
+
* plausible alone, and together they produced a form whose checkbox, phone
|
|
207
|
+
* field and wheel ignored the theme every other control followed.
|
|
208
|
+
*
|
|
209
|
+
* The narrowing matters as much as the fallback: a caller may pass a variant the
|
|
210
|
+
* theme recipes have no case for (`ghost`, `link`, or a value read from storage
|
|
211
|
+
* written by an older release). Coercing to `solid` renders a plain control;
|
|
212
|
+
* passing it through renders an *unstyled* one, because a recipe silently emits
|
|
213
|
+
* nothing for a variant it does not define.
|
|
214
|
+
*
|
|
215
|
+
* ## Pass `allowed` when your recipe defines more than the theme five
|
|
216
|
+
*
|
|
217
|
+
* The default list is the five appearances a user can select app-wide, and for
|
|
218
|
+
* most controls that is the right gate. But some recipes define extras that are
|
|
219
|
+
* reachable per-call-site and not offerable globally — `inputBoolRecipe` has
|
|
220
|
+
* `ghost` and `none`. Narrowing those to `solid` is silent: the control renders,
|
|
221
|
+
* it just quietly ignores what the call site asked for.
|
|
222
|
+
*
|
|
223
|
+
* That is not hypothetical. Migrating `StyledInputBool` into this package with
|
|
224
|
+
* the default list dropped `variant="ghost"` on the floor, and nothing failed
|
|
225
|
+
* except one test in the consuming app that happened to assert it.
|
|
226
|
+
*
|
|
227
|
+
* `allowed` must contain `"solid"`, since that is the fallback.
|
|
228
|
+
*/
|
|
229
|
+
export function useResolvedVariant(variant?: string): ThemeVariant;
|
|
230
|
+
export function useResolvedVariant<T extends string>(
|
|
231
|
+
variant: string | undefined,
|
|
232
|
+
allowed: readonly T[],
|
|
233
|
+
): T;
|
|
234
|
+
export function useResolvedVariant(
|
|
235
|
+
variant?: string,
|
|
236
|
+
allowed: readonly string[] = THEME_VARIANTS,
|
|
237
|
+
): string {
|
|
238
|
+
const globalVariant = useStyleConfig().variant;
|
|
239
|
+
const candidate = variant ?? globalVariant;
|
|
240
|
+
return allowed.includes(candidate) ? candidate : "solid";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The user's density preference, as they chose it.
|
|
245
|
+
*
|
|
246
|
+
* Relative to the app's baseline, so it answers "did the user ask for tighter?"
|
|
247
|
+
* rather than "how much padding is there?". `useDensityStep` answers the
|
|
248
|
+
* second. `compact` is what makes intent buttons icon-only.
|
|
249
|
+
*/
|
|
250
|
+
export function useDensity(): DensityProfile {
|
|
251
|
+
return useStyleConfig().density;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Where the app's baseline and the user's preference actually land.
|
|
256
|
+
*
|
|
257
|
+
* This is the value that maps to real spacing — feed it to
|
|
258
|
+
* `densityCustomProperties` to get the properties the recipes read.
|
|
259
|
+
*/
|
|
260
|
+
export function useDensityStep(): DensityStep {
|
|
261
|
+
const { densityBase, density } = useStyleConfig();
|
|
262
|
+
return resolveDensityStep(densityBase, density);
|
|
263
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary shared by the recipes and the components.
|
|
3
|
+
*
|
|
4
|
+
* Ported from HopperGuard's `hopper-types` so this package depends on nothing
|
|
5
|
+
* private. The tuples are the source of truth — the unions derive from them, so
|
|
6
|
+
* a value can be validated at runtime and narrowed at compile time from one
|
|
7
|
+
* declaration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The five appearances a user can choose app-wide. Every recipe defines all
|
|
12
|
+
* five, which is why `staticCss` force-generates them (see the preset).
|
|
13
|
+
*/
|
|
14
|
+
export const THEME_VARIANTS = [
|
|
15
|
+
"solid",
|
|
16
|
+
"outline",
|
|
17
|
+
"aurora",
|
|
18
|
+
"glass",
|
|
19
|
+
"matte",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Appearances a *call site* can ask for but a user cannot select globally —
|
|
24
|
+
* a `ghost` toolbar button stays ghost whatever the app-wide variant is.
|
|
25
|
+
*/
|
|
26
|
+
export const STYLE_VARIANTS = [
|
|
27
|
+
"ghost",
|
|
28
|
+
"none",
|
|
29
|
+
"link",
|
|
30
|
+
"unstyled",
|
|
31
|
+
"selected",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
/** Semantic text treatments. Not appearances — meanings. */
|
|
35
|
+
export const TEXT_VARIANTS = ["unstyled", "error", "pop", "warning"] as const;
|
|
36
|
+
|
|
37
|
+
export const ALL_VARIANTS = [...THEME_VARIANTS, ...STYLE_VARIANTS] as const;
|
|
38
|
+
|
|
39
|
+
export type ThemeVariant = (typeof THEME_VARIANTS)[number];
|
|
40
|
+
export type StyleVariant = (typeof STYLE_VARIANTS)[number];
|
|
41
|
+
export type AllowedTextVariant = (typeof TEXT_VARIANTS)[number];
|
|
42
|
+
export type AllowedVariant = (typeof ALL_VARIANTS)[number];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The user's app-wide text-size setting.
|
|
46
|
+
*
|
|
47
|
+
* Five tiers, not the thirteen `fontSizeMap` defines: the extra sizes exist so a
|
|
48
|
+
* heading can step one above body text, but only these five are offerable as a
|
|
49
|
+
* global preference.
|
|
50
|
+
*/
|
|
51
|
+
export const FONT_SIZE_PROFILES = ["xs", "sm", "md", "lg", "xl"] as const;
|
|
52
|
+
export type FontSizeProfile = (typeof FONT_SIZE_PROFILES)[number];
|
|
53
|
+
|
|
54
|
+
/** Every size key `fontSizeMap` understands, including the heading-only tiers. */
|
|
55
|
+
export const FONT_SIZE_KEYS = [
|
|
56
|
+
"xs",
|
|
57
|
+
"sm",
|
|
58
|
+
"md",
|
|
59
|
+
"lg",
|
|
60
|
+
"xl",
|
|
61
|
+
"2xl",
|
|
62
|
+
"3xl",
|
|
63
|
+
"4xl",
|
|
64
|
+
"5xl",
|
|
65
|
+
"6xl",
|
|
66
|
+
"7xl",
|
|
67
|
+
"8xl",
|
|
68
|
+
"9xl",
|
|
69
|
+
] as const;
|
|
70
|
+
export type FontSizeKey = (typeof FONT_SIZE_KEYS)[number];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The icon size vocabulary.
|
|
74
|
+
*
|
|
75
|
+
* Deliberately the Font Awesome scale, because ~150 call sites in the
|
|
76
|
+
* originating app are written against it and silently changing what `"2x"`
|
|
77
|
+
* means would have been an invisible, app-wide visual change. It is just a set
|
|
78
|
+
* of names here — no icon library is implied by using them.
|
|
79
|
+
*
|
|
80
|
+
* It covers **all** of Font Awesome's `SizeProp`, `2xs` and `2xl` included.
|
|
81
|
+
* That completeness is the point: an adapter that maps its library's size type
|
|
82
|
+
* onto this one has to be able to, and an incomplete union turns into a type
|
|
83
|
+
* error at every call site that happens to use a missing value. `md` is the one
|
|
84
|
+
* addition, an alias for `1x`.
|
|
85
|
+
*
|
|
86
|
+
* It lives here rather than beside `StyledIcon` because `StyleConfig` names it —
|
|
87
|
+
* the app-wide default icon size is a host setting — and having the config
|
|
88
|
+
* import the component that imports the config would be a cycle.
|
|
89
|
+
*/
|
|
90
|
+
export const ICON_SIZES = [
|
|
91
|
+
"2xs",
|
|
92
|
+
"xs",
|
|
93
|
+
"sm",
|
|
94
|
+
"1x",
|
|
95
|
+
"md",
|
|
96
|
+
"lg",
|
|
97
|
+
"2x",
|
|
98
|
+
"xl",
|
|
99
|
+
"2xl",
|
|
100
|
+
"3x",
|
|
101
|
+
"4x",
|
|
102
|
+
"5x",
|
|
103
|
+
"6x",
|
|
104
|
+
"7x",
|
|
105
|
+
"8x",
|
|
106
|
+
"9x",
|
|
107
|
+
"10x",
|
|
108
|
+
] as const;
|
|
109
|
+
export type IconSize = (typeof ICON_SIZES)[number];
|
|
110
|
+
|
|
111
|
+
export function isThemeVariant(value: unknown): value is ThemeVariant {
|
|
112
|
+
return THEME_VARIANTS.includes(value as ThemeVariant);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isFontSizeProfile(value: unknown): value is FontSizeProfile {
|
|
116
|
+
return FONT_SIZE_PROFILES.includes(value as FontSizeProfile);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* How tightly the UI packs. A user preference, not a viewport thing — someone
|
|
121
|
+
* with limited dexterity picks `comfortable` on a phone, and someone managing a
|
|
122
|
+
* dense list picks `compact` on a desktop.
|
|
123
|
+
*
|
|
124
|
+
* It changes more than padding: at `compact` the intent buttons drop their
|
|
125
|
+
* text label and show only their icon, which is why the shared components need
|
|
126
|
+
* to know about it rather than the host handling it alone.
|
|
127
|
+
*/
|
|
128
|
+
export const DENSITY_PROFILES = ["compact", "normal", "comfortable"] as const;
|
|
129
|
+
export type DensityProfile = (typeof DENSITY_PROFILES)[number];
|
|
130
|
+
|
|
131
|
+
export function isDensityProfile(value: unknown): value is DensityProfile {
|
|
132
|
+
return DENSITY_PROFILES.includes(value as DensityProfile);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function isIconSize(value: unknown): value is IconSize {
|
|
136
|
+
return ICON_SIZES.includes(value as IconSize);
|
|
137
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stonedogcode/style — public API.
|
|
3
|
+
*
|
|
4
|
+
* Components are exported both as named exports and, individually, as default
|
|
5
|
+
* exports from their own module. Prefer the named export; the default exports
|
|
6
|
+
* exist because the originating codebase used them at ~1,400 call sites and
|
|
7
|
+
* changing that was not worth bundling into the extraction.
|
|
8
|
+
*
|
|
9
|
+
* The Panda preset is NOT re-exported here. It is imported by the consumer's
|
|
10
|
+
* `panda.config.ts`, which runs in Node at build time, and pulling the whole
|
|
11
|
+
* component tree (and React with it) into that context is a needless cost —
|
|
12
|
+
* hence the separate `@stonedogcode/style/preset` entry point.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Configuration seam — what a host application must wire up.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export {
|
|
19
|
+
StonedogStyleProvider,
|
|
20
|
+
useStyleConfig,
|
|
21
|
+
useFontSizeProfile,
|
|
22
|
+
useIconSize,
|
|
23
|
+
useResolvedVariant,
|
|
24
|
+
DEFAULT_STYLE_CONFIG,
|
|
25
|
+
} from "./config/style-config";
|
|
26
|
+
export type {
|
|
27
|
+
StyleConfig,
|
|
28
|
+
StonedogStyleProviderProps,
|
|
29
|
+
} from "./config/style-config";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Deprecated `Hopper*` aliases — NEH-251. See `config/style-config.tsx`.
|
|
33
|
+
* Removed once every consumer has landed its rename PR.
|
|
34
|
+
*/
|
|
35
|
+
export { HopperStyleProvider } from "./config/style-config";
|
|
36
|
+
export type { HopperStyleProviderProps } from "./config/style-config";
|
|
37
|
+
|
|
38
|
+
export { setStyleLogger } from "./config/logger";
|
|
39
|
+
export type { StyleLogger } from "./config/logger";
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
fontSizeMap,
|
|
43
|
+
getFontSizeLabel,
|
|
44
|
+
getFontSizeValue,
|
|
45
|
+
stepUpFontSize,
|
|
46
|
+
FONT_SIZE_ORDER,
|
|
47
|
+
} from "./config/font-size";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
THEME_VARIANTS,
|
|
51
|
+
STYLE_VARIANTS,
|
|
52
|
+
TEXT_VARIANTS,
|
|
53
|
+
ALL_VARIANTS,
|
|
54
|
+
FONT_SIZE_PROFILES,
|
|
55
|
+
FONT_SIZE_KEYS,
|
|
56
|
+
ICON_SIZES,
|
|
57
|
+
isThemeVariant,
|
|
58
|
+
isFontSizeProfile,
|
|
59
|
+
isIconSize,
|
|
60
|
+
} from "./config/types";
|
|
61
|
+
export type {
|
|
62
|
+
ThemeVariant,
|
|
63
|
+
StyleVariant,
|
|
64
|
+
AllowedVariant,
|
|
65
|
+
AllowedTextVariant,
|
|
66
|
+
FontSizeProfile,
|
|
67
|
+
FontSizeKey,
|
|
68
|
+
IconSize,
|
|
69
|
+
} from "./config/types";
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Layout primitives
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
export { default as StyledBox, StyledBox as Box } from "./components/StyledBox";
|
|
75
|
+
export type { StyledBoxProps } from "./components/StyledBox";
|
|
76
|
+
|
|
77
|
+
export { default as StyledFlex } from "./components/StyledFlex";
|
|
78
|
+
export type { StyledFlexProps } from "./components/StyledFlex";
|
|
79
|
+
|
|
80
|
+
export { default as StyledGrid } from "./components/StyledGrid";
|
|
81
|
+
export type { StyledGridProps } from "./components/StyledGrid";
|
|
82
|
+
|
|
83
|
+
export { default as StyledGridItem } from "./components/StyledGridItem";
|
|
84
|
+
export type { StyledGridItemProps } from "./components/StyledGridItem";
|
|
85
|
+
|
|
86
|
+
export { default as StyledSimpleGrid } from "./components/StyledSimpleGrid";
|
|
87
|
+
|
|
88
|
+
export { default as StyledStack } from "./components/StyledStack";
|
|
89
|
+
export type { StyledStackProps } from "./components/StyledStack";
|
|
90
|
+
|
|
91
|
+
export { default as StyledHStack } from "./components/StyledHStack";
|
|
92
|
+
export type { StyledHStackProps } from "./components/StyledHStack";
|
|
93
|
+
|
|
94
|
+
export { default as StyledVStack } from "./components/StyledVStack";
|
|
95
|
+
export type { StyledVStackProps } from "./components/StyledVStack";
|
|
96
|
+
|
|
97
|
+
export { default as StyledScrollbar } from "./components/StyledScrollbar";
|
|
98
|
+
export { default as StyledSidebar, StyledSidebar as Sidebar } from "./components/StyledSidebar";
|
|
99
|
+
export type { StyledSidebarProps, SidebarItem } from "./components/StyledSidebar";
|
|
100
|
+
export { default as StyledCollapsible } from "./components/StyledCollapsible";
|
|
101
|
+
export type { StyledCollapsibleProps } from "./components/StyledCollapsible";
|
|
102
|
+
|
|
103
|
+
export { default as StyledFooter, StyledFooter as Footer } from "./components/StyledFooter";
|
|
104
|
+
export type {
|
|
105
|
+
StyledFooterProps,
|
|
106
|
+
StyledFooterVersion,
|
|
107
|
+
StyledFooterStatusBadge,
|
|
108
|
+
} from "./components/StyledFooter";
|
|
109
|
+
export type { StyledScrollbarProps } from "./components/StyledScrollbar";
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Typography & dividers
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
export { default as StyledText } from "./components/StyledText";
|
|
115
|
+
export type { StyledTextProps } from "./components/StyledText";
|
|
116
|
+
|
|
117
|
+
export { default as StyledHeading } from "./components/StyledHeading";
|
|
118
|
+
|
|
119
|
+
export { default as StyledSeparator } from "./components/StyledSeparator";
|
|
120
|
+
export type { StyledSeparatorProps } from "./components/StyledSeparator";
|
|
121
|
+
|
|
122
|
+
export { default as StyledHrRule } from "./components/StyledHrRule";
|
|
123
|
+
export type { StyledHrRuleProps } from "./components/StyledHrRule";
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
default as TitleLogo,
|
|
127
|
+
TITLE_LOGO_METRICS,
|
|
128
|
+
TITLE_LOGO_SIZES,
|
|
129
|
+
isTitleLogoSize,
|
|
130
|
+
} from "./components/TitleLogo";
|
|
131
|
+
export type { TitleLogoProps, TitleLogoSize } from "./components/TitleLogo";
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Icons — the seam, not the artwork. See the README.
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
export { default as StyledIcon } from "./components/StyledIcon";
|
|
137
|
+
// `IconSize` is exported above, from `config/types` — it is a config-level
|
|
138
|
+
// vocabulary now that `StyleConfig` names it. `StyledIcon` still re-exports it
|
|
139
|
+
// so the older import path keeps working for consumers.
|
|
140
|
+
export type { StyledIconProps } from "./components/StyledIcon";
|
|
141
|
+
export { createIcon, createIconFromComponent } from "./components/create-icon";
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Overlay
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
export * from "./components/intent-buttons";
|
|
147
|
+
export { createIntentButton } from "./components/create-intent-button";
|
|
148
|
+
export type { IntentButtonProps, IntentButtonSpec } from "./components/create-intent-button";
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
IntentIconProvider,
|
|
152
|
+
useIntentIcon,
|
|
153
|
+
useIntentIcons,
|
|
154
|
+
missingIntentIcons,
|
|
155
|
+
ICON_INTENTS,
|
|
156
|
+
} from "./config/intent-icons";
|
|
157
|
+
export type { IconIntent, IntentIcons } from "./config/intent-icons";
|
|
158
|
+
|
|
159
|
+
export { useDensity, useDensityStep } from "./config/style-config";
|
|
160
|
+
export { DENSITY_PROFILES, isDensityProfile } from "./config/types";
|
|
161
|
+
export type { DensityProfile } from "./config/types";
|
|
162
|
+
export {
|
|
163
|
+
DENSITY_STEPS,
|
|
164
|
+
DENSITY_BASES,
|
|
165
|
+
DENSITY_METRICS,
|
|
166
|
+
densityCustomProperties,
|
|
167
|
+
isDensityBase,
|
|
168
|
+
isDensityStep,
|
|
169
|
+
resolveDensityStep,
|
|
170
|
+
} from "./config/density";
|
|
171
|
+
export type { DensityBase, DensityStep } from "./config/density";
|
|
172
|
+
|
|
173
|
+
export { default as StyledButton } from "./components/StyledButton";
|
|
174
|
+
export type { StyledButtonProps } from "./components/StyledButton";
|
|
175
|
+
|
|
176
|
+
export { default as StyledIconButton } from "./components/StyledIconButton";
|
|
177
|
+
export type { StyledIconButtonProps, IconButtonSize } from "./components/StyledIconButton";
|
|
178
|
+
|
|
179
|
+
export { default as StyledSpinner } from "./components/StyledSpinner";
|
|
180
|
+
export type { StyledSpinnerProps } from "./components/StyledSpinner";
|
|
181
|
+
|
|
182
|
+
export { default as StyledTooltip } from "./components/StyledTooltip";
|
|
183
|
+
// Exported so a host can make the same decision for its own controls. A product
|
|
184
|
+
// that renders its own hover-revealed affordance has the identical problem, and
|
|
185
|
+
// re-deriving "can this device hover" per app is how two answers appear.
|
|
186
|
+
export { useCanHover } from "./config/can-hover";
|
|
187
|
+
export type { StyledTooltipProps } from "./components/StyledTooltip";
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Form controls
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
export { default as StyledFormLabel } from "./components/StyledFormLabel";
|
|
193
|
+
export type { StyledFormLabelProps } from "./components/StyledFormLabel";
|
|
194
|
+
|
|
195
|
+
export { default as StyledInputBool } from "./components/StyledInputBool";
|
|
196
|
+
export type { StyledInputBoolProps, InputBoolVariant } from "./components/StyledInputBool";
|
|
197
|
+
export { INPUT_BOOL_VARIANTS } from "./components/StyledInputBool";
|
|
198
|
+
|
|
199
|
+
export { default as StyledInputSlider } from "./components/StyledInputSlider";
|
|
200
|
+
export type { StyledInputSliderProps } from "./components/StyledInputSlider";
|
|
201
|
+
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Text inputs — dictation is supplied by the host, never implemented here
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
export type { Dictation } from "./components/dictation";
|
|
206
|
+
|
|
207
|
+
export { default as StyledInputText } from "./components/StyledInputText";
|
|
208
|
+
export type { StyledInputTextProps, InputTextVariant } from "./components/StyledInputText";
|
|
209
|
+
export { INPUT_TEXT_VARIANTS } from "./components/StyledInputText";
|
|
210
|
+
|
|
211
|
+
export { default as StyledInputTextArea } from "./components/StyledInputTextArea";
|
|
212
|
+
export type { StyledInputTextAreaProps } from "./components/StyledInputTextArea";
|
|
213
|
+
export { default as DictationPrompt } from "./components/DictationPrompt";
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Select + search
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
export { default as StyledInputSelect } from "./components/StyledInputSelect";
|
|
219
|
+
export type {
|
|
220
|
+
StyledInputSelectProps,
|
|
221
|
+
SelectOption,
|
|
222
|
+
SelectVariant,
|
|
223
|
+
} from "./components/StyledInputSelect";
|
|
224
|
+
export { SELECT_VARIANTS } from "./components/StyledInputSelect";
|
|
225
|
+
|
|
226
|
+
export { default as StyledSearch } from "./components/StyledSearch";
|
|
227
|
+
export type { StyledSearchProps } from "./components/StyledSearch";
|
|
228
|
+
|
|
229
|
+
export { default as StyledInputToggle } from "./components/StyledInputToggle";
|
|
230
|
+
export type { StyledInputToggleProps } from "./components/StyledInputToggle";
|
|
231
|
+
|
|
232
|
+
export { default as StyledInputRadio } from "./components/StyledInputRadio";
|
|
233
|
+
export type { StyledInputRadioProps, RadioItem, RadioVariant } from "./components/StyledInputRadio";
|
|
234
|
+
export { RADIO_VARIANTS } from "./components/StyledInputRadio";
|
|
235
|
+
|
|
236
|
+
export { default as StyledFieldset } from "./components/StyledFieldset";
|
|
237
|
+
export type { StyledFieldsetProps, FieldsetVariant } from "./components/StyledFieldset";
|
|
238
|
+
export { FIELDSET_VARIANTS } from "./components/StyledFieldset";
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Data display
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
export { default as StyledList } from "./components/StyledList";
|
|
244
|
+
export type {
|
|
245
|
+
StyledListRootProps,
|
|
246
|
+
StyledListItemProps,
|
|
247
|
+
ListVariant,
|
|
248
|
+
} from "./components/StyledList";
|
|
249
|
+
export { LIST_VARIANTS } from "./components/StyledList";
|
|
250
|
+
|
|
251
|
+
export { default as StyledDefinitionList } from "./components/StyledDefinitionList";
|
|
252
|
+
export type {
|
|
253
|
+
StyledDefinitionListProps,
|
|
254
|
+
DlVariant,
|
|
255
|
+
} from "./components/StyledDefinitionList";
|
|
256
|
+
export { DL_VARIANTS } from "./components/StyledDefinitionList";
|
|
257
|
+
|
|
258
|
+
export { default as StyledSparkLine } from "./components/StyledSparkLine";
|
|
259
|
+
export type { StyledSparkLineProps } from "./components/StyledSparkLine";
|