@lotics/ui 42.0.0 → 42.3.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/docs/catalog.md +15 -0
- package/docs/composition.md +23 -2
- package/package.json +10 -3
- package/src/avatar.tsx +6 -1
- package/src/avatar.web.tsx +6 -1
- package/src/avatar_size.ts +10 -1
- package/src/color_tokens.ts +422 -0
- package/src/colors.ts +14 -383
- package/src/colors.web.ts +43 -0
- package/src/summary.tsx +217 -0
- package/src/summary_line.tsx +23 -0
- package/src/theme.tsx +13 -50
- package/src/theme.web.tsx +64 -0
- package/src/theme_context.ts +45 -0
package/src/summary_line.tsx
CHANGED
|
@@ -20,6 +20,21 @@ export interface SummaryLineItem {
|
|
|
20
20
|
trend?: number | null;
|
|
21
21
|
/** What this metric means — the ⓘ next to the value opens it in a popover. */
|
|
22
22
|
info?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Swatch colour, when this line is ALSO the legend for a chart above it —
|
|
25
|
+
* typically a `StackedProgressBar` whose segments are these same aggregates.
|
|
26
|
+
*
|
|
27
|
+
* It exists so a bar can be labelled without giving up what this component
|
|
28
|
+
* already carries. `LegendItem` annotates a chart but holds no `info`, so
|
|
29
|
+
* reaching for it costs the popovers that explain what a bucket includes and
|
|
30
|
+
* what it must not be added to — and those explanations are the reason several
|
|
31
|
+
* of these lines are trustworthy. One swatch here keeps identity off colour
|
|
32
|
+
* alone, which is what a legend is for.
|
|
33
|
+
*
|
|
34
|
+
* Pass the SAME value the segment uses. Omit it and the item renders exactly
|
|
35
|
+
* as it always has.
|
|
36
|
+
*/
|
|
37
|
+
color?: string;
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
export interface SummaryLineProps {
|
|
@@ -55,6 +70,7 @@ export function SummaryLine(props: SummaryLineProps) {
|
|
|
55
70
|
<View style={styles.row}>
|
|
56
71
|
{items.map((item) => (
|
|
57
72
|
<View key={item.label} style={styles.item}>
|
|
73
|
+
{item.color ? <View style={[styles.swatch, { backgroundColor: item.color }]} /> : null}
|
|
58
74
|
<Metric
|
|
59
75
|
value={item.value}
|
|
60
76
|
format={item.format}
|
|
@@ -89,4 +105,11 @@ const styles = StyleSheet.create({
|
|
|
89
105
|
alignItems: "center",
|
|
90
106
|
gap: 6,
|
|
91
107
|
},
|
|
108
|
+
// Matches `LegendItem`'s swatch, because the two annotate the same charts and
|
|
109
|
+
// a reader should not be able to tell which component drew the legend.
|
|
110
|
+
swatch: {
|
|
111
|
+
width: 8,
|
|
112
|
+
height: 8,
|
|
113
|
+
borderRadius: 2,
|
|
114
|
+
},
|
|
92
115
|
});
|
package/src/theme.tsx
CHANGED
|
@@ -1,61 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { LoticsThemeContext, type LoticsTheme } from "./theme_context";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
* Default platform accent — refined OKLCH blue. Used by chart fills,
|
|
5
|
-
* focus rings, and any primitive that asks "what's the brand color".
|
|
6
|
-
* Apps that need a different accent wrap their root in `LoticsThemeProvider`.
|
|
7
|
-
*
|
|
8
|
-
* Why OKLCH instead of hex? Perceptual uniformity — `oklch(0.6 0.118 250)`
|
|
9
|
-
* sits at the same perceptual lightness/saturation as the `oklch(0.6 0.118
|
|
10
|
-
* 184.704)` (teal) chị's workspace uses, just shifted in hue. Hex shifts
|
|
11
|
-
* lightness as hue rotates and the eye picks it up as inconsistency.
|
|
12
|
-
*/
|
|
13
|
-
export const DEFAULT_ACCENT = "oklch(0.6 0.118 250)";
|
|
14
|
-
|
|
15
|
-
interface LoticsTheme {
|
|
16
|
-
/** Single brand accent. Chart fills, hero CTAs, focus rings. */
|
|
17
|
-
accent: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const LoticsThemeContext = createContext<LoticsTheme>({ accent: DEFAULT_ACCENT });
|
|
4
|
+
export { DEFAULT_ACCENT, useLoticsTheme, THEME_VARS, type LoticsTheme } from "./theme_context";
|
|
21
5
|
|
|
22
|
-
interface LoticsThemeProviderProps {
|
|
23
|
-
/** Brand accent. Overrides the platform default. Accepts any CSS color
|
|
24
|
-
* value (OKLCH recommended, hex / hsl also fine). */
|
|
25
|
-
accent?: string;
|
|
6
|
+
interface LoticsThemeProviderProps extends LoticsTheme {
|
|
26
7
|
children: ReactNode;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
10
|
/**
|
|
30
|
-
* App-root provider
|
|
31
|
-
* Wrap your top-level app element to override the platform defaults:
|
|
11
|
+
* App-root provider — the NATIVE half.
|
|
32
12
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
13
|
+
* On native there are no CSS variables, and `colors` is a literal table, so
|
|
14
|
+
* there is nothing to declare: the provider publishes the values for anything
|
|
15
|
+
* that reads them through `useLoticsTheme()` and renders its children
|
|
16
|
+
* untouched. The web half (`theme.web.tsx`) is where a theme actually paints.
|
|
37
17
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* customization without needing a different provider.
|
|
41
|
-
*
|
|
42
|
-
* Scope is intentionally narrow — accent only. Semantic colors (success,
|
|
43
|
-
* danger) already work via existing `colors.green[600]` etc. Adding more
|
|
44
|
-
* theme tokens requires a real product reason.
|
|
18
|
+
* Deliberately NOT a `View`: a wrapper here would insert a layout box into every
|
|
19
|
+
* themed app's tree on the one platform that gains nothing from it.
|
|
45
20
|
*/
|
|
46
21
|
export function LoticsThemeProvider(props: LoticsThemeProviderProps) {
|
|
47
|
-
const
|
|
48
|
-
return
|
|
49
|
-
<LoticsThemeContext.Provider value={{ accent }}>
|
|
50
|
-
{props.children}
|
|
51
|
-
</LoticsThemeContext.Provider>
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Read the current theme. Primitives that need the accent color call this
|
|
57
|
-
* hook; apps don't need it directly (use the provider's prop instead).
|
|
58
|
-
*/
|
|
59
|
-
export function useLoticsTheme(): LoticsTheme {
|
|
60
|
-
return useContext(LoticsThemeContext);
|
|
22
|
+
const { children, ...theme } = props;
|
|
23
|
+
return <LoticsThemeContext.Provider value={theme}>{children}</LoticsThemeContext.Provider>;
|
|
61
24
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useMemo, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { LoticsThemeContext, THEME_VARS, type LoticsTheme } from "./theme_context";
|
|
3
|
+
|
|
4
|
+
export { DEFAULT_ACCENT, useLoticsTheme, THEME_VARS, type LoticsTheme } from "./theme_context";
|
|
5
|
+
|
|
6
|
+
interface LoticsThemeProviderProps extends LoticsTheme {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* App-root provider that gives `@lotics/ui` primitives this app's identity:
|
|
12
|
+
*
|
|
13
|
+
* // src/main.tsx
|
|
14
|
+
* <LoticsThemeProvider accent="#0F766E" border="#E3E8E6">
|
|
15
|
+
* <App />
|
|
16
|
+
* </LoticsThemeProvider>
|
|
17
|
+
*
|
|
18
|
+
* It declares the CSS custom properties `colors.web.ts` reads, so every kit
|
|
19
|
+
* component below re-paints without knowing the provider exists. That
|
|
20
|
+
* indirection is the point: the alternative — a context each primitive
|
|
21
|
+
* subscribes to — would have to be threaded through 136 modules and would turn
|
|
22
|
+
* their static stylesheets into per-render inline styles.
|
|
23
|
+
*
|
|
24
|
+
* **It renders a plain `div`, not a `View`, and that is load-bearing.**
|
|
25
|
+
* react-native-web's style compiler only emits properties it knows; a custom
|
|
26
|
+
* property handed to a `View` is silently DROPPED, so the variables never reach
|
|
27
|
+
* the DOM and a themed app renders in the platform defaults with no error
|
|
28
|
+
* anywhere. React DOM, by contrast, writes `--*` inline style keys through
|
|
29
|
+
* verbatim. The failure is invisible rather than loud, which is exactly why the
|
|
30
|
+
* two platforms get two files instead of one clever component.
|
|
31
|
+
*
|
|
32
|
+
* Only a role actually PASSED is declared, so an omitted one leaves its variable
|
|
33
|
+
* undefined and `colors.web.ts`'s inline fallback answers. That is what makes
|
|
34
|
+
* theming additive: a partial theme overrides a part, never resetting the rest
|
|
35
|
+
* to some second set of defaults.
|
|
36
|
+
*/
|
|
37
|
+
export function LoticsThemeProvider(props: LoticsThemeProviderProps) {
|
|
38
|
+
const { children, ...theme } = props;
|
|
39
|
+
|
|
40
|
+
// Keyed on the four values, not the object: the call site is normally an
|
|
41
|
+
// inline literal, which would otherwise rebuild this style — and re-render
|
|
42
|
+
// every kit surface under it — on each parent render.
|
|
43
|
+
const style = useMemo(() => {
|
|
44
|
+
const vars: Record<string, string> = {};
|
|
45
|
+
for (const [role, name] of Object.entries(THEME_VARS)) {
|
|
46
|
+
const value = theme[role as keyof LoticsTheme];
|
|
47
|
+
if (value !== undefined) vars[name] = value;
|
|
48
|
+
}
|
|
49
|
+
// `display: contents` — the provider generates NO box. Custom properties
|
|
50
|
+
// inherit down the DOM tree rather than the box tree, so the variables still
|
|
51
|
+
// reach every descendant while the element itself adds no layout at all.
|
|
52
|
+
// The first version was a flex column with `flex: 1`, which silently imposed
|
|
53
|
+
// a layout contract on every app that wrapped its root in this: a provider
|
|
54
|
+
// whose entire job is declaring three strings has no business deciding how its
|
|
55
|
+
// children stack.
|
|
56
|
+
return { display: "contents", ...vars } as CSSProperties;
|
|
57
|
+
}, [theme.accent, theme.background, theme.border]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div style={style}>
|
|
61
|
+
<LoticsThemeContext.Provider value={theme}>{children}</LoticsThemeContext.Provider>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { colors } from "./color_tokens";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The platform's own accent — what every kit surface wears unthemed. Same value
|
|
6
|
+
* `colors.accent` falls back to; named so an app can reference the default
|
|
7
|
+
* explicitly instead of re-typing a literal.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_ACCENT = colors.accent;
|
|
10
|
+
|
|
11
|
+
/** The roles an app may own. See `color_tokens.ts` for why exactly these. */
|
|
12
|
+
export interface LoticsTheme {
|
|
13
|
+
/** The one brand hue. It paints IDENTITY marks — today the avatar's initials
|
|
14
|
+
* disc — and deliberately NOT the focus ring or the primary action, which stay
|
|
15
|
+
* neutral so those read the same in every app. Never a status, a series or a
|
|
16
|
+
* valence: those come from a palette family and carry meaning. */
|
|
17
|
+
accent?: string;
|
|
18
|
+
/** The surface Card, Drawer and Modal paint — 21 sites in the kit. Left white
|
|
19
|
+
* on almost every app; set it only for a deliberately toned surface. */
|
|
20
|
+
background?: string;
|
|
21
|
+
/** Hairlines — 48 sites in the kit, and a register is mostly these lines, so a
|
|
22
|
+
* small change here is felt across a whole screen. */
|
|
23
|
+
border?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The CSS custom properties `colors.web.ts` reads. Part of the published
|
|
28
|
+
* contract: an app may set them from its own stylesheet instead of the provider.
|
|
29
|
+
*/
|
|
30
|
+
export const THEME_VARS: Record<keyof LoticsTheme, string> = {
|
|
31
|
+
accent: "--lotics-accent",
|
|
32
|
+
background: "--lotics-background",
|
|
33
|
+
border: "--lotics-border",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const LoticsThemeContext = createContext<LoticsTheme>({});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read the current theme. An app SETS values through the provider's props; this
|
|
40
|
+
* is for a surface that must branch on one (a chart picking a fill). A role the
|
|
41
|
+
* app did not set reads `undefined` — ask `colors.<role>` for what will paint.
|
|
42
|
+
*/
|
|
43
|
+
export function useLoticsTheme(): LoticsTheme {
|
|
44
|
+
return useContext(LoticsThemeContext);
|
|
45
|
+
}
|