@spark-web/text 0.0.0-snapshot-release-20260409001813

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/CLAUDE.md ADDED
@@ -0,0 +1,91 @@
1
+ # @spark-web/text — AI Context
2
+
3
+ ## What this is
4
+
5
+ The primary text rendering component. Applies theme typography tokens (size,
6
+ weight, tone, baseline trimming) and renders as a block `div` by default. Use
7
+ for body copy, labels, captions, status messages, and cell content inside
8
+ tables.
9
+
10
+ ## What this is NOT
11
+
12
+ - Not a heading — use `@spark-web/heading` for semantic headings (h1–h4)
13
+ - Not a link — use `@spark-web/text-link` for anchor text
14
+ - Not a layout container — do not put layout logic inside `Text`
15
+
16
+ ## Props interface
17
+
18
+ | Prop | Type | Default | Notes |
19
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------- |
20
+ | `size` | `'xsmall' \| 'small' \| 'standard' \| 'large'` | `'standard'` | Typography scale |
21
+ | `tone` | `'neutral' \| 'muted' \| 'link' \| 'disabled' \| 'field' \| 'fieldAccent' \| 'placeholder' \| 'accent' \| 'primary' \| 'primaryHover' \| 'primaryActive' \| 'secondary' \| 'secondaryHover' \| 'secondaryActive' \| 'caution' \| 'critical' \| 'info' \| 'positive' \| 'dark' \| 'brandSubtle'` | `'neutral'` | Text color |
22
+ | `weight` | `'regular' \| 'semibold'` | — | Font weight override |
23
+ | `align` | `'left' \| 'center' \| 'right'` | — | Block variant only (not compatible with `inline`) |
24
+ | `inline` | `boolean` | `false` | Renders as `<span>` with `display: inline` |
25
+ | `baseline` | `boolean` | `true` | Apply leading-trim (cap-height) — set `false` when not adjacent to other baseline-trimmed elements |
26
+ | `overflowStrategy` | `'truncate' \| 'nowrap'` | — | Block variant only |
27
+ | `numberOfLines` | `number` | — | Clamp to N lines with ellipsis |
28
+ | `tabularNumbers` | `boolean` | `false` | Monospace-width numerals |
29
+ | `transform` | CSS `textTransform` value | — | `'uppercase'`, `'capitalize'`, etc. |
30
+ | `as` | HTML element tag | `'div'` | Use `'span'`, `'p'`, `'label'`, etc. as needed |
31
+ | `data` | `DataAttributeMap` | — | Test/analytics attributes |
32
+
33
+ ## Token usage
34
+
35
+ All typography values come from `theme.typography`. Text internally uses
36
+ `useText()` which maps `size` → responsive font-size/line-height and applies
37
+ baseline trimming. Never compute font sizes or colors directly — always use
38
+ `Text` props.
39
+
40
+ ## Common patterns
41
+
42
+ ### Table cell content
43
+
44
+ ```tsx
45
+ // Always size="small" in table cells
46
+ <Text size="small">{row.name}</Text>
47
+ ```
48
+
49
+ ### Muted supporting text
50
+
51
+ ```tsx
52
+ <Text tone="muted" size="small">
53
+ No records found
54
+ </Text>
55
+ ```
56
+
57
+ ### Inline label within a block
58
+
59
+ ```tsx
60
+ <Text>
61
+ Status:{' '}
62
+ <Text inline tone="positive">
63
+ Active
64
+ </Text>
65
+ </Text>
66
+ ```
67
+
68
+ ### Truncate long values
69
+
70
+ ```tsx
71
+ <Text size="small" overflowStrategy="truncate">
72
+ {longValue}
73
+ </Text>
74
+ ```
75
+
76
+ ## Composition
77
+
78
+ `Text` renders a `Box` internally. It provides a `TextContext` so nested `Text`
79
+ components inherit `size`, `tone`, and `weight` — override only what differs
80
+ from the parent.
81
+
82
+ ## Do NOTs
83
+
84
+ - NEVER use raw CSS for font-size, color, or line-height — always use `Text`
85
+ props
86
+ - NEVER use `tone="neutralInverted"` or `tone="mutedInverted"` directly — these
87
+ are automatic inversions applied by the `background` context
88
+ - NEVER set `baseline={true}` (the default) on a `Text` that is not inside a
89
+ layout component that accounts for leading-trim — set `baseline={false}` when
90
+ in doubt for non-standard layouts
91
+ - NEVER use `Text` for headings — use `@spark-web/heading`
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: Text
3
+ storybookPath: typography-text--default
4
+ isExperimentalPackage: false
5
+ ---
6
+
7
+ Constrained, purposeful text styles as a component.
8
+
9
+ ## Examples
10
+
11
+ ```jsx live
12
+ const textSizes = ['large', 'standard', 'small', 'xsmall'];
13
+
14
+ return (
15
+ <Columns collapseBelow="tablet" gap="xlarge">
16
+ <Stack gap="large">
17
+ {textSizes.map(textSize => (
18
+ <Text key={textSize} size={textSize} weight="regular">
19
+ Text {textSize} regular
20
+ </Text>
21
+ ))}
22
+ </Stack>
23
+ <Stack gap="large">
24
+ {textSizes.map(textSize => (
25
+ <Text key={textSize} size={textSize} weight="semibold">
26
+ Text {textSize} regular
27
+ </Text>
28
+ ))}
29
+ </Stack>
30
+ </Columns>
31
+ );
32
+ ```
33
+
34
+ ### Align
35
+
36
+ Text can be aligned with the `align` prop.
37
+
38
+ ```jsx live
39
+ <Stack gap="large" dividers>
40
+ <Text align="left">Left (default)</Text>
41
+ <Text align="center">Center</Text>
42
+ <Text align="right">Right</Text>
43
+ </Stack>
44
+ ```
45
+
46
+ ### Overflow strategy
47
+
48
+ Use the `overflowStrategy` prop to manage how `Text` behaves with regard to
49
+ overflow.
50
+
51
+ ```jsx live
52
+ const overflowStrategies = ['truncate', 'nowrap', 'breakword'];
53
+
54
+ return (
55
+ <Stack gap="large" style={{ width: 200 }}>
56
+ <Stack gap="small">
57
+ <Text weight="semibold">Default</Text>
58
+ <Text>The quick brown fox jumps over the lazy dog.</Text>
59
+ </Stack>
60
+ {overflowStrategies.map(overflowStrategy => (
61
+ <Stack key={overflowStrategy} gap="small">
62
+ <Text weight="semibold">{overflowStrategy}</Text>
63
+ <Text overflowStrategy={overflowStrategy}>
64
+ The quick brown fox jumps over the lazy dog.
65
+ </Text>
66
+ </Stack>
67
+ ))}
68
+ </Stack>
69
+ );
70
+ ```
71
+
72
+ ### Tone
73
+
74
+ The foreground colour of text can be set by applying a `tone`. In addition to
75
+ the foundation tones, “muted” provides a way to de-emphasise text.
76
+
77
+ ```jsx live
78
+ const textTones = [
79
+ 'neutral', // Default
80
+ 'accent',
81
+ 'caution',
82
+ 'critical',
83
+ 'disabled',
84
+ 'fieldAccent',
85
+ 'info',
86
+ 'link',
87
+ 'muted',
88
+ 'placeholder',
89
+ 'positive',
90
+ 'primary',
91
+ 'primaryActive',
92
+ 'primaryHover',
93
+ 'secondary',
94
+ 'secondaryActive',
95
+ 'secondaryHover',
96
+ ];
97
+
98
+ return (
99
+ <Columns collapseBelow="tablet" gap="large" template={[1, 1]}>
100
+ {textTones.map(tone => (
101
+ <Text key={tone} tone={tone}>
102
+ {tone}
103
+ </Text>
104
+ ))}
105
+ </Columns>
106
+ );
107
+ ```
108
+
109
+ ### Weight
110
+
111
+ Text is available in two weight: `regular` and `semibold`.
112
+
113
+ ```jsx live
114
+ <Inline gap="small">
115
+ <Text weight="regular">Regular</Text>
116
+ <Text weight="semibold">Semibold</Text>
117
+ </Inline>
118
+ ```
119
+
120
+ ### Contrast
121
+
122
+ To ensure text has sufficient contrast, when on a dark background the foreground
123
+ tones “neutral” and “muted” will be inverted.
124
+
125
+ ```jsx live
126
+ <Inline gap="large">
127
+ <Box background="neutral" padding="small" borderRadius="small">
128
+ <Text>neutral</Text>
129
+ </Box>
130
+ <Box background="neutral" padding="small" borderRadius="small">
131
+ <Text tone="muted">muted</Text>
132
+ </Box>
133
+ </Inline>
134
+ ```
135
+
136
+ ## Props
137
+
138
+ <PropsTable displayName="Text" />
139
+
140
+ Extra props are also passed into the underlying [`Box`](/package/box) component.
141
+
142
+ [brighte-theme]:
143
+ https://github.com/brighte-labs/spark-web/blob/e503bea4f7668d187ec7a78f99c5ed374417588b/packages/theme/src/makeTheme.ts#L158
144
+ [data-attribute-map]:
145
+ https://github.com/brighte-labs/spark-web/blob/e7f6f4285b4cfd876312cc89fbdd094039aa239a/packages/utils/src/internal/buildDataAttributes.ts#L1
@@ -0,0 +1,10 @@
1
+ export declare const TextContext: import("react").Context<{
2
+ size: NonNullable<import("@spark-web/theme/src/themes/_types/typography").TypographySizing>;
3
+ tone: NonNullable<string | number>;
4
+ weight: NonNullable<"bold" | "medium" | "light" | "thin" | "black" | "extralight" | "regular" | "semibold" | "extrabold" | undefined> | undefined;
5
+ } | undefined>;
6
+ export declare function useTextContext(): {
7
+ size: NonNullable<import("@spark-web/theme/src/themes/_types/typography").TypographySizing>;
8
+ tone: NonNullable<string | number>;
9
+ weight: NonNullable<"bold" | "medium" | "light" | "thin" | "black" | "extralight" | "regular" | "semibold" | "extrabold" | undefined> | undefined;
10
+ } | undefined;
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { UseTextProps } from "./use-text.js";
3
+ type DefaultTextProps = {
4
+ size?: NonNullable<UseTextProps['size']>;
5
+ tone?: NonNullable<UseTextProps['tone']>;
6
+ weight?: NonNullable<UseTextProps['weight']>;
7
+ };
8
+ export declare function DefaultTextPropsProvider({ children, size, tone, weight, }: DefaultTextProps & {
9
+ children: ReactNode;
10
+ }): import("@emotion/react/jsx-runtime").JSX.Element;
11
+ export declare const useDefaultTextProps: ({ size: sizeProp, tone: toneProp, weight: weightProp, }: DefaultTextProps) => {
12
+ size: NonNullable<import("@spark-web/theme/src/themes/_types/typography").TypographySizing>;
13
+ tone: NonNullable<string | number>;
14
+ weight: NonNullable<"bold" | "medium" | "light" | "thin" | "black" | "extralight" | "regular" | "semibold" | "extrabold" | undefined> | undefined;
15
+ };
16
+ export {};
@@ -0,0 +1,12 @@
1
+ export { useTextContext } from "./context.js";
2
+ export { DefaultTextPropsProvider, useDefaultTextProps, } from "./default-text-props.js";
3
+ export { Strong } from "./strong.js";
4
+ export { Text } from "./text.js";
5
+ export { useForegroundTone } from "./use-foreground-tone.js";
6
+ export { useNumberOfLines } from "./use-number-of-lines.js";
7
+ export { useOverflowStrategy } from "./use-overflow-strategy.js";
8
+ export { createTextStyles, useText } from "./use-text.js";
9
+ export type { StrongProps } from "./strong.js";
10
+ export type { TextProps } from "./text.js";
11
+ export type { ForegroundTone } from "./use-foreground-tone.js";
12
+ export type { TextOverflowStrategy } from "./use-overflow-strategy.js";
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+ export type StrongProps = {
3
+ children: ReactNode;
4
+ };
5
+ export declare const Strong: ({ children }: StrongProps) => import("@emotion/react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import type { CSSObject } from '@emotion/react';
2
+ import type { BoxProps } from '@spark-web/box';
3
+ import type { DataAttributeMap } from '@spark-web/utils/internal';
4
+ import type { CSSProperties, ReactNode } from 'react';
5
+ import type { TextOverflowStrategy } from "./use-overflow-strategy.js";
6
+ import type { UseTextProps } from "./use-text.js";
7
+ type InlineProps = {
8
+ align?: never;
9
+ /** Display as an inline element. */
10
+ inline?: boolean;
11
+ overflowStrategy?: never;
12
+ };
13
+ type BlockProps = {
14
+ /** The horizontal alignment. */
15
+ align?: 'left' | 'center' | 'right';
16
+ inline?: never;
17
+ /** Manage how text behaves with regard to overflow. */
18
+ overflowStrategy?: TextOverflowStrategy;
19
+ };
20
+ export type TextProps = Partial<UseTextProps> & {
21
+ /** The text content. */
22
+ children?: ReactNode;
23
+ /** Customizes the component element. */
24
+ css?: CSSObject;
25
+ /** Sets data attributes on the component. */
26
+ data?: DataAttributeMap;
27
+ /** An identifier which must be unique in the whole document. */
28
+ id?: BoxProps['id'];
29
+ /** When enabled, numbers will be the same width. Similar to a monospaced font. */
30
+ tabularNumbers?: boolean;
31
+ /** Transform the text casing. */
32
+ transform?: CSSProperties['textTransform'];
33
+ /** Truncate the text with an ellipsis after computing the text layout, including line wrapping,
34
+ * such that the total number of lines does not exceed this number. */
35
+ numberOfLines?: number;
36
+ } & (InlineProps | BlockProps);
37
+ export declare const Text: <Comp extends import("react").ElementType = "div">(props: {
38
+ as?: Comp | undefined;
39
+ ref?: import("react").Ref<Comp extends "symbol" | "svg" | "animate" | "animateMotion" | "animateTransform" | "circle" | "clipPath" | "defs" | "desc" | "ellipse" | "feBlend" | "feColorMatrix" | "feComponentTransfer" | "feComposite" | "feConvolveMatrix" | "feDiffuseLighting" | "feDisplacementMap" | "feDistantLight" | "feDropShadow" | "feFlood" | "feFuncA" | "feFuncB" | "feFuncG" | "feFuncR" | "feGaussianBlur" | "feImage" | "feMerge" | "feMergeNode" | "feMorphology" | "feOffset" | "fePointLight" | "feSpecularLighting" | "feSpotLight" | "feTile" | "feTurbulence" | "filter" | "foreignObject" | "g" | "image" | "line" | "linearGradient" | "marker" | "mask" | "metadata" | "mpath" | "path" | "pattern" | "polygon" | "polyline" | "radialGradient" | "rect" | "set" | "stop" | "switch" | "text" | "textPath" | "tspan" | "use" | "view" | keyof HTMLElementTagNameMap ? (HTMLElementTagNameMap & Pick<SVGElementTagNameMap, "symbol" | "svg" | "animate" | "animateMotion" | "animateTransform" | "circle" | "clipPath" | "defs" | "desc" | "ellipse" | "feBlend" | "feColorMatrix" | "feComponentTransfer" | "feComposite" | "feConvolveMatrix" | "feDiffuseLighting" | "feDisplacementMap" | "feDistantLight" | "feDropShadow" | "feFlood" | "feFuncA" | "feFuncB" | "feFuncG" | "feFuncR" | "feGaussianBlur" | "feImage" | "feMerge" | "feMergeNode" | "feMorphology" | "feOffset" | "fePointLight" | "feSpecularLighting" | "feSpotLight" | "feTile" | "feTurbulence" | "filter" | "foreignObject" | "g" | "image" | "line" | "linearGradient" | "marker" | "mask" | "metadata" | "mpath" | "path" | "pattern" | "polygon" | "polyline" | "radialGradient" | "rect" | "set" | "stop" | "switch" | "text" | "textPath" | "tspan" | "use" | "view">)[Comp] : Comp extends new (...args: any) => any ? InstanceType<Comp> : undefined> | undefined;
40
+ } & (Omit<import("react").PropsWithoutRef<import("react").ComponentProps<Comp>>, "as"> & TextProps)) => import("react").ReactElement;
41
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { SparkTheme } from '@spark-web/theme';
2
+ export type ForegroundTone = keyof Omit<SparkTheme['color']['foreground'], 'neutralInverted' | 'mutedInverted'>;
3
+ export declare function useForegroundTone(tone: ForegroundTone): string;
@@ -0,0 +1,2 @@
1
+ import type { CSSProperties } from 'react';
2
+ export declare const useNumberOfLines: (numberOfLines?: number) => CSSProperties;
@@ -0,0 +1,27 @@
1
+ declare const strategyMap: {
2
+ readonly truncate: {
3
+ readonly display: "block";
4
+ readonly overflow: "hidden";
5
+ readonly textOverflow: "ellipsis";
6
+ readonly whiteSpace: "nowrap";
7
+ };
8
+ readonly nowrap: {
9
+ readonly whiteSpace: "nowrap";
10
+ };
11
+ readonly breakword: {
12
+ readonly display: "block";
13
+ readonly overflowWrap: "break-word";
14
+ readonly wordBreak: "break-word";
15
+ readonly wordWrap: "break-word";
16
+ };
17
+ };
18
+ export type TextOverflowStrategy = keyof typeof strategyMap;
19
+ export declare function useOverflowStrategy(strategy?: TextOverflowStrategy): {
20
+ readonly whiteSpace: "nowrap";
21
+ } | {
22
+ readonly display: "block";
23
+ readonly overflowWrap: "break-word";
24
+ readonly wordBreak: "break-word";
25
+ readonly wordWrap: "break-word";
26
+ } | null;
27
+ export {};
@@ -0,0 +1,31 @@
1
+ import type { SparkTextDefinition, SparkTheme } from '@spark-web/theme';
2
+ import type { ForegroundTone } from "./use-foreground-tone.js";
3
+ export type UseTextProps = {
4
+ /** Apply leading-trim styles. */
5
+ baseline?: boolean;
6
+ /** The size of the text. */
7
+ size: keyof SparkTheme['typography']['text'];
8
+ /** The tone of the text. */
9
+ tone: ForegroundTone;
10
+ /** The weight of the text. */
11
+ weight?: keyof SparkTheme['typography']['fontWeight'];
12
+ };
13
+ export declare function useText({ baseline, size, tone, weight }: UseTextProps): (import("@emotion/serialize").CSSObject | undefined)[];
14
+ export declare function createTextStyles({ fontSize, fontWeight, lineHeight, trims }: SparkTextDefinition, { includeTrims, includeWeight }?: {
15
+ includeTrims?: boolean | undefined;
16
+ includeWeight?: boolean | undefined;
17
+ }): {
18
+ '::before'?: {
19
+ marginBottom: string;
20
+ content: string;
21
+ display: string;
22
+ } | undefined;
23
+ '::after'?: {
24
+ marginTop: string;
25
+ content: string;
26
+ display: string;
27
+ } | undefined;
28
+ fontSize: string;
29
+ fontWeight: number | undefined;
30
+ lineHeight: string;
31
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3Bhcmstd2ViLXRleHQuY2pzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvaW5kZXguZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9