@castui/cast-ui 4.2.2 → 4.4.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/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # Cast UI
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Connagh/cast-ui/main/logo.png" alt="Cast UI" width="300" />
3
+ </p>
2
4
 
3
5
  A cross-platform component library for React Native. One set of components
4
6
  that works on iOS, Android, and the web.
@@ -95,6 +97,7 @@ the icons you use (e.g. `pyftsubset` from
95
97
  | **Radio** | Single-choice control, with `RadioGroup` for managing a set |
96
98
  | **Select** | Dropdown with single, multi (tag pills), and combobox (search) modes |
97
99
  | **Skeleton** | Loading placeholder in text, circle, and rectangle shapes |
100
+ | **Text** | Typographic primitive rendering the full type ramp, from caption to display |
98
101
  | **Toast** | Brief notification with icon and optional close button |
99
102
  | **Toggle** | On/off switch with label |
100
103
  | **Tooltip** | Short hint shown on hover or focus |
@@ -15,8 +15,6 @@ const LABEL_SCALE = {
15
15
  default: 'md',
16
16
  large: 'lg',
17
17
  };
18
- /** Icon size — fixed at 16px, matched to the label baseline. */
19
- const ICON_SIZE = 16;
20
18
  function resolveColors(intent, variant, intentColors) {
21
19
  const clrs = intentColors[intent];
22
20
  switch (variant) {
@@ -38,8 +36,8 @@ function Badge({ children, intent = 'neutral', variant = 'solid', size = 'defaul
38
36
  const sizeTokens = components.badge[size];
39
37
  const labelTokens = tokens_1.label[LABEL_SCALE[size]];
40
38
  const colors = resolveColors(intent, variant, scheme.intents);
41
- const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: colors.fg })) : (leadingIcon);
42
- const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: ICON_SIZE, color: colors.fg })) : (trailingIcon);
39
+ const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: colors.fg })) : (leadingIcon);
40
+ const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: size, color: colors.fg })) : (trailingIcon);
43
41
  return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { accessibilityRole: "text", accessibilityLabel: accessibilityLabel || children, style: [
44
42
  {
45
43
  flexDirection: 'row',
@@ -60,12 +58,12 @@ function Badge({ children, intent = 'neutral', variant = 'solid', size = 'defaul
60
58
  height: sizeTokens.dotSize,
61
59
  borderRadius: sizeTokens.dotSize / 2,
62
60
  backgroundColor: colors.fg,
63
- } })) : null, resolvedLeading ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedLeading })) : null, (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
61
+ } })) : null, resolvedLeading ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[size], height: tokens_1.iconSize[size] }, children: resolvedLeading })) : null, (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
64
62
  fontFamily: tokens_1.fontFamily.sans,
65
63
  fontWeight: tokens_1.fontWeight.medium,
66
64
  fontSize: labelTokens.fontSize,
67
65
  lineHeight: labelTokens.lineHeight,
68
66
  letterSpacing: labelTokens.letterSpacing,
69
67
  color: colors.fg,
70
- }, selectable: false, children: children }), resolvedTrailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedTrailing })) : null] }));
68
+ }, selectable: false, children: children }), resolvedTrailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[size], height: tokens_1.iconSize[size] }, children: resolvedTrailing })) : null] }));
71
69
  }
@@ -36,4 +36,9 @@ export type ButtonProps = {
36
36
  /** Accessibility label — falls back to children text if not provided. */
37
37
  accessibilityLabel?: string;
38
38
  };
39
+ /**
40
+ * Icon size scales with the button size, matching the Figma <Icon> size
41
+ * variant 1:1 (small→16, default→20, large→24). Button's size names
42
+ * are a subset of the Icon named scale, so `size` is passed straight through.
43
+ */
39
44
  export declare function Button({ children, intent, prominence, size, disabled, leadingIcon, trailingIcon, onPress, style, accessibilityLabel, }: ButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -28,8 +28,11 @@ const LABEL_SCALE = {
28
28
  default: 'md',
29
29
  large: 'lg',
30
30
  };
31
- /** Icon size — fixed at 16px per Figma spec, all button sizes. */
32
- const ICON_SIZE = 16;
31
+ /**
32
+ * Icon size scales with the button size, matching the Figma <Icon> size
33
+ * variant 1:1 (small→16, default→20, large→24). Button's size names
34
+ * are a subset of the Icon named scale, so `size` is passed straight through.
35
+ */
33
36
  // ---------------------------------------------------------------------------
34
37
  // Component
35
38
  // ---------------------------------------------------------------------------
@@ -71,8 +74,8 @@ function Button({ children, intent = 'neutral', prominence = 'default', size = '
71
74
  backgroundColor: stateColors.bg,
72
75
  };
73
76
  // Resolve icon props — strings become <Icon> with auto-matched colour
74
- const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: stateColors.fg })) : (leadingIcon);
75
- const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: ICON_SIZE, color: stateColors.fg })) : (trailingIcon);
77
+ const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: stateColors.fg })) : (leadingIcon);
78
+ const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: size, color: stateColors.fg })) : (trailingIcon);
76
79
  return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: containerStyle, children: [resolvedLeading, (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
77
80
  fontFamily: tokens_1.fontFamily.sans,
78
81
  fontWeight: tokens_1.fontWeight.medium,
@@ -24,11 +24,12 @@
24
24
  * font is loaded). On native they require a matching static/variable font cut.
25
25
  */
26
26
  import { type TextStyle, type StyleProp } from 'react-native';
27
+ import { type IconSize } from '../../tokens';
27
28
  export type IconProps = {
28
29
  /** Material Symbols icon name (e.g., "star", "close", "settings"). */
29
30
  name: string;
30
- /** Icon size in pixels. Defaults to 20. */
31
- size?: number;
31
+ /** Icon size a named scale ('xs' | 'small' | 'default' | 'large' = 12/16/20/24) or an explicit pixel number. Defaults to 20. */
32
+ size?: IconSize | number;
32
33
  /** Icon colour. Defaults to "#374151" (neutral fg). */
33
34
  color?: string;
34
35
  /** Filled vs outlined glyph (FILL axis, 0–1). Defaults to false (outlined). */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Icon = Icon;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_native_1 = require("react-native");
6
+ const tokens_1 = require("../../tokens");
6
7
  // Web accepts both family names so the Google-Fonts CSS path (spaced name)
7
8
  // and the expo-font/useFonts path (unspaced key) work without consumer
8
9
  // configuration. Fonts registered via expo-font on web keep their object key
@@ -12,8 +13,9 @@ const FONT_FAMILY = react_native_1.Platform.select({
12
13
  default: 'MaterialSymbolsOutlined',
13
14
  });
14
15
  function Icon({ name, size = 20, color = '#374151', fill = false, weight = 400, grade = 0, opticalSize, style, }) {
16
+ const px = typeof size === 'number' ? size : tokens_1.iconSize[size];
15
17
  // Material Symbols variable-font axes — applied on web via fontVariationSettings.
16
- const opsz = Math.min(48, Math.max(20, opticalSize ?? size));
18
+ const opsz = Math.min(48, Math.max(20, opticalSize ?? px));
17
19
  const variationStyle = react_native_1.Platform.OS === 'web'
18
20
  ? {
19
21
  fontVariationSettings: `'FILL' ${fill ? 1 : 0}, 'wght' ${weight}, 'GRAD' ${grade}, 'opsz' ${opsz}`,
@@ -22,12 +24,12 @@ function Icon({ name, size = 20, color = '#374151', fill = false, weight = 400,
22
24
  return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: false, accessibilityElementsHidden: true, importantForAccessibility: "no", style: [
23
25
  {
24
26
  fontFamily: FONT_FAMILY,
25
- fontSize: size,
26
- lineHeight: size,
27
+ fontSize: px,
28
+ lineHeight: px,
27
29
  color,
28
30
  // Prevent ligature text from taking extra space
29
- width: size,
30
- height: size,
31
+ width: px,
32
+ height: px,
31
33
  textAlign: 'center',
32
34
  // Reset any inherited text styles
33
35
  fontWeight: '400',
@@ -1 +1,2 @@
1
1
  export { Icon, type IconProps } from './Icon';
2
+ export { type IconSize } from '../../tokens';
@@ -26,7 +26,8 @@ const tokens_1 = require("../../tokens");
26
26
  // ---------------------------------------------------------------------------
27
27
  // Constants
28
28
  // ---------------------------------------------------------------------------
29
- const ICON_SIZE = 16;
29
+ /** Icon size scales with the input size, matching the Figma <Icon> size
30
+ * variant 1:1 (small→16, default→20, large→24) via the shared `iconSize` scale. */
30
31
  /** Maps input size → label typography scale (form label text) */
31
32
  const LABEL_SCALE = {
32
33
  small: 'sm',
@@ -90,8 +91,8 @@ function Input({ label: formLabel, helperText, placeholder, value, defaultValue,
90
91
  ? errorTokens.fg
91
92
  : textTokens.description;
92
93
  const iconColor = disabled ? disabledColors.fg : neutral.default.fg;
93
- const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: iconColor })) : (leadingIcon);
94
- const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: ICON_SIZE, color: iconColor })) : (trailingIcon);
94
+ const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: iconColor })) : (leadingIcon);
95
+ const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: size, color: iconColor })) : (trailingIcon);
95
96
  return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
96
97
  { alignSelf: 'stretch', gap: components.input.fieldGap },
97
98
  style,
@@ -112,7 +113,7 @@ function Input({ label: formLabel, helperText, placeholder, value, defaultValue,
112
113
  borderWidth,
113
114
  borderColor,
114
115
  backgroundColor: bg,
115
- }, children: [resolvedLeading ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedLeading })) : null, (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: textTokens.placeholder, editable: !disabled, secureTextEntry: secureTextEntry, keyboardType: keyboardType, autoCapitalize: autoCapitalize, returnKeyType: returnKeyType, onSubmitEditing: onSubmitEditing, onFocus: () => {
116
+ }, children: [resolvedLeading ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[size], height: tokens_1.iconSize[size] }, children: resolvedLeading })) : null, (0, jsx_runtime_1.jsx)(react_native_1.TextInput, { value: value, defaultValue: defaultValue, onChangeText: onChangeText, placeholder: placeholder, placeholderTextColor: textTokens.placeholder, editable: !disabled, secureTextEntry: secureTextEntry, keyboardType: keyboardType, autoCapitalize: autoCapitalize, returnKeyType: returnKeyType, onSubmitEditing: onSubmitEditing, onFocus: () => {
116
117
  setIsFocused(true);
117
118
  onFocus?.();
118
119
  }, onBlur: () => {
@@ -130,7 +131,7 @@ function Input({ label: formLabel, helperText, placeholder, value, defaultValue,
130
131
  ...(react_native_1.Platform.OS === 'web'
131
132
  ? { outlineWidth: 0 }
132
133
  : {}),
133
- } }), resolvedTrailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedTrailing })) : null] }), helperText ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
134
+ } }), resolvedTrailing ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[size], height: tokens_1.iconSize[size] }, children: resolvedTrailing })) : null] }), helperText ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
134
135
  fontFamily: tokens_1.fontFamily.sans,
135
136
  fontWeight: tokens_1.fontWeight.regular,
136
137
  fontSize: tokens_1.caption.fontSize,
@@ -41,8 +41,9 @@ function useSelectContext() {
41
41
  // ---------------------------------------------------------------------------
42
42
  // Constants
43
43
  // ---------------------------------------------------------------------------
44
- const ICON_SIZE = 16;
45
- const CHEVRON_SIZE = 16;
44
+ /** Icon sizes scale with the select size via the shared `iconSize` scale
45
+ * (small→16, default→20, large→24): trigger leading icon, dropdown chevron,
46
+ * and option row icons all track the control size. Tag close uses `closeSize`. */
46
47
  const CONTENT_MAX_HEIGHT = 240;
47
48
  /** Maps select size → label typography scale (for form label text) */
48
49
  const LABEL_SCALE = {
@@ -153,7 +154,7 @@ function SelectOption({ value, children: optionLabel, description, icon, disable
153
154
  : isHovered
154
155
  ? selectColors.option.hover
155
156
  : selectColors.option.default;
156
- const resolvedIcon = typeof icon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: icon, size: ICON_SIZE, color: colors.fg })) : (icon);
157
+ const resolvedIcon = typeof icon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: icon, size: ctx.size, color: colors.fg })) : (icon);
157
158
  return ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPress: () => {
158
159
  if (!disabled)
159
160
  ctx.onSelect(value);
@@ -165,7 +166,7 @@ function SelectOption({ value, children: optionLabel, description, icon, disable
165
166
  paddingVertical: tokens.paddingY,
166
167
  borderRadius: tokens.borderRadius,
167
168
  backgroundColor: colors.bg,
168
- }, children: [resolvedIcon ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedIcon })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
169
+ }, children: [resolvedIcon ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[ctx.size], height: tokens_1.iconSize[ctx.size] }, children: resolvedIcon })) : null, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: { flex: 1, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: {
169
170
  fontFamily: tokens_1.fontFamily.sans,
170
171
  fontWeight: tokens_1.fontWeight.medium,
171
172
  fontSize: labelTokens.fontSize,
@@ -179,7 +180,7 @@ function SelectOption({ value, children: optionLabel, description, icon, disable
179
180
  lineHeight: bodyTokens.lineHeight,
180
181
  letterSpacing: bodyTokens.letterSpacing,
181
182
  color: disabled ? colors.fg : textTokens.description,
182
- }, numberOfLines: 1, selectable: false, children: description })) : null] }), isSelected && !disabled ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "check", size: ICON_SIZE, color: colors.fg }) })) : null] }));
183
+ }, numberOfLines: 1, selectable: false, children: description })) : null] }), isSelected && !disabled ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[ctx.size], height: tokens_1.iconSize[ctx.size] }, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "check", size: ctx.size, color: colors.fg }) })) : null] }));
183
184
  }
184
185
  function SelectContent({ children, style }) {
185
186
  const { components, scheme } = (0, theme_1.useTheme)();
@@ -265,7 +266,7 @@ function Select({ type = 'single', size = 'default', label: formLabel, helperTex
265
266
  ? disabledColors.fg
266
267
  : intentColors.neutral.default.default.fg;
267
268
  // Resolve leading icon
268
- const resolvedLeadingIcon = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: ICON_SIZE, color: triggerFgColor })) : (leadingIcon);
269
+ const resolvedLeadingIcon = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: triggerFgColor })) : (leadingIcon);
269
270
  // Get display text for single select
270
271
  const getSelectedLabel = () => {
271
272
  if (type === 'single' || type === 'combobox') {
@@ -313,7 +314,7 @@ function Select({ type = 'single', size = 'default', label: formLabel, helperTex
313
314
  borderWidth: tokens_1.controlTokens.borderWidth,
314
315
  borderColor: triggerBorderColor,
315
316
  backgroundColor: triggerBgColor,
316
- }, children: [resolvedLeadingIcon ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: ICON_SIZE, height: ICON_SIZE }, children: resolvedLeadingIcon })) : null, type === 'multi' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
317
+ }, children: [resolvedLeadingIcon ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { width: tokens_1.iconSize[size], height: tokens_1.iconSize[size] }, children: resolvedLeadingIcon })) : null, type === 'multi' ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
317
318
  flex: 1,
318
319
  flexDirection: 'row',
319
320
  flexWrap: 'wrap',
@@ -359,7 +360,7 @@ function Select({ type = 'single', size = 'default', label: formLabel, helperTex
359
360
  : textTokens.description,
360
361
  }, selectable: false, children: displayText ?? placeholder }) })), (0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: isOpen
361
362
  ? { transform: [{ rotate: '180deg' }] }
362
- : undefined, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "keyboard_arrow_down", size: CHEVRON_SIZE, color: triggerFgColor }) })] }), isOpen ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => setIsOpen(false), accessibilityRole: "button", accessibilityLabel: "Close select", style: react_native_1.Platform.select({
363
+ : undefined, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "arrow_drop_down", size: size, color: triggerFgColor }) })] }), isOpen ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => setIsOpen(false), accessibilityRole: "button", accessibilityLabel: "Close select", style: react_native_1.Platform.select({
363
364
  web: {
364
365
  position: 'fixed',
365
366
  top: 0,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Text — the typographic primitive of Cast UI, rendering the full type ramp.
3
+ *
4
+ * Maps to the Figma <Text> component (node 769:2710):
5
+ * type → caption | label-sm/md/lg | body-sm/md/lg | title-sm/md/lg |
6
+ * heading-sm/md/lg | display-sm/md/lg
7
+ *
8
+ * Each `type` applies the matching Figma Text Style — Inter at the scale's
9
+ * size/line-height/tracking, weighted per family: label and title render
10
+ * medium (500), heading renders semibold (600), body, caption, and display
11
+ * render regular (400).
12
+ *
13
+ * Colour defaults to the active scheme's text/primary variable (cool-grey/700
14
+ * in light mode, cool-grey/200 in dark) so text follows `colorMode`
15
+ * automatically; pass `color` to override. Typography is constant across
16
+ * densities — Text reads no density tokens.
17
+ *
18
+ * Requires the Inter font to be loaded — see the README "Fonts" section.
19
+ */
20
+ import { type StyleProp, type TextStyle } from 'react-native';
21
+ export type TextType = 'caption' | 'label-sm' | 'label-md' | 'label-lg' | 'body-sm' | 'body-md' | 'body-lg' | 'title-sm' | 'title-md' | 'title-lg' | 'heading-sm' | 'heading-md' | 'heading-lg' | 'display-sm' | 'display-md' | 'display-lg';
22
+ export type TextProps = {
23
+ /** The text content. */
24
+ children: string;
25
+ /** Type ramp entry — mirrors the Figma `type` variant. Defaults to "body-md". */
26
+ type?: TextType;
27
+ /** Text colour. Defaults to the active scheme's text/primary. */
28
+ color?: string;
29
+ /** Truncate to this many lines with an ellipsis. */
30
+ numberOfLines?: number;
31
+ /** Whether the text can be selected/copied. Defaults to the platform default. */
32
+ selectable?: boolean;
33
+ /** Style overrides — applied after the type ramp styles. */
34
+ style?: StyleProp<TextStyle>;
35
+ /** Accessibility label — falls back to the text content. */
36
+ accessibilityLabel?: string;
37
+ };
38
+ export declare function Text({ children, type, color, numberOfLines, selectable, style, accessibilityLabel, }: TextProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Text = Text;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_native_1 = require("react-native");
6
+ const theme_1 = require("../../theme");
7
+ const tokens_1 = require("../../tokens");
8
+ /** type → resolved scale + per-family weight (matches the Figma Text Styles) */
9
+ const TYPE_STYLES = {
10
+ caption: { ...tokens_1.caption, fontWeight: tokens_1.fontWeight.regular },
11
+ 'label-sm': { ...tokens_1.label.sm, fontWeight: tokens_1.fontWeight.medium },
12
+ 'label-md': { ...tokens_1.label.md, fontWeight: tokens_1.fontWeight.medium },
13
+ 'label-lg': { ...tokens_1.label.lg, fontWeight: tokens_1.fontWeight.medium },
14
+ 'body-sm': { ...tokens_1.body.sm, fontWeight: tokens_1.fontWeight.regular },
15
+ 'body-md': { ...tokens_1.body.md, fontWeight: tokens_1.fontWeight.regular },
16
+ 'body-lg': { ...tokens_1.body.lg, fontWeight: tokens_1.fontWeight.regular },
17
+ 'title-sm': { ...tokens_1.title.sm, fontWeight: tokens_1.fontWeight.medium },
18
+ 'title-md': { ...tokens_1.title.md, fontWeight: tokens_1.fontWeight.medium },
19
+ 'title-lg': { ...tokens_1.title.lg, fontWeight: tokens_1.fontWeight.medium },
20
+ 'heading-sm': { ...tokens_1.heading.sm, fontWeight: tokens_1.fontWeight.semibold },
21
+ 'heading-md': { ...tokens_1.heading.md, fontWeight: tokens_1.fontWeight.semibold },
22
+ 'heading-lg': { ...tokens_1.heading.lg, fontWeight: tokens_1.fontWeight.semibold },
23
+ 'display-sm': { ...tokens_1.display.sm, fontWeight: tokens_1.fontWeight.regular },
24
+ 'display-md': { ...tokens_1.display.md, fontWeight: tokens_1.fontWeight.regular },
25
+ 'display-lg': { ...tokens_1.display.lg, fontWeight: tokens_1.fontWeight.regular },
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // Component
29
+ // ---------------------------------------------------------------------------
30
+ function Text({ children, type = 'body-md', color, numberOfLines, selectable, style, accessibilityLabel, }) {
31
+ const { scheme } = (0, theme_1.useTheme)();
32
+ const typeStyle = TYPE_STYLES[type];
33
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: numberOfLines, selectable: selectable, accessibilityRole: type.startsWith('heading') ? 'header' : undefined, accessibilityLabel: accessibilityLabel, style: [
34
+ {
35
+ fontFamily: tokens_1.fontFamily.sans,
36
+ color: color ?? scheme.text.primary,
37
+ ...typeStyle,
38
+ },
39
+ style,
40
+ ], children: children }));
41
+ }
@@ -0,0 +1 @@
1
+ export { Text, type TextProps, type TextType } from './Text';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Text = void 0;
4
+ var Text_1 = require("./Text");
5
+ Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return Text_1.Text; } });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, radioColors, avatarColors, skeletonColors, fontFamily, fontWeight, label, title, body, caption, type IntentName, type ProminenceName, type StateName, type ColorMode, type ColorScheme, type LabelSize, } from './tokens';
1
+ export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, radioColors, avatarColors, skeletonColors, fontFamily, fontWeight, label, title, body, heading, display, caption, type IntentName, type ProminenceName, type StateName, type ColorMode, type ColorScheme, type LabelSize, iconSize, type IconSize, } from './tokens';
2
2
  export { ThemeProvider, useTheme, themes, type Theme, type ThemeProviderProps, type DensityTheme, type ComponentTokens, type ButtonSizeTokens, type ButtonThemeTokens, type DialogSizeTokens, type DialogThemeTokens, type InputSizeTokens, type InputThemeTokens, type SelectContentTokens, type SelectOptionTokens, type SelectGroupTokens, type SelectSeparatorTokens, type SelectThemeTokens, type ListItemTokens, type ListSubheaderTokens, type ListThemeTokens, type CheckboxSizeTokens, type CheckboxThemeTokens, type AlertSizeTokens, type AlertThemeTokens, type ToggleSizeTokens, type ToggleThemeTokens, type CardSizeTokens, type CardThemeTokens, type BadgeSizeTokens, type BadgeThemeTokens, type RadioSizeTokens, type RadioThemeTokens, type ToastSizeTokens, type ToastThemeTokens, type ChipSizeTokens, type ChipThemeTokens, type AvatarSizeTokens, type AvatarThemeTokens, type PopoverSizeTokens, type PopoverThemeTokens, type TooltipSizeTokens, type TooltipThemeTokens, type DeepPartial, } from './theme';
3
3
  export { Button, type ButtonProps, type ButtonSize } from './components/Button';
4
4
  export { Icon, type IconProps } from './components/Icon';
@@ -19,3 +19,4 @@ export { Avatar, type AvatarProps, type AvatarSize, type AvatarType, } from './c
19
19
  export { Popover, type PopoverProps, type PopoverSize, type PopoverDirection, } from './components/Popover';
20
20
  export { Skeleton, type SkeletonProps, type SkeletonShape, } from './components/Skeleton';
21
21
  export { Tooltip, type TooltipProps, type TooltipSize, type TooltipDirection, } from './components/Tooltip';
22
+ export { Text, type TextProps, type TextType } from './components/Text';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Toast = exports.RadioGroup = exports.Radio = exports.Input = exports.Badge = exports.Card = exports.Toggle = exports.Alert = exports.Checkbox = exports.ListDivider = exports.ListSubheader = exports.ListItem = exports.List = exports.SelectDropdown = exports.SelectTag = exports.SelectSeparator = exports.SelectGroup = exports.SelectOption = exports.Select = exports.DialogContent = exports.Dialog = exports.Icon = exports.Button = exports.themes = exports.useTheme = exports.ThemeProvider = exports.caption = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = exports.skeletonColors = exports.avatarColors = exports.radioColors = exports.toggleColors = exports.checkboxColors = exports.listColors = exports.errorTokens = exports.tagTokens = exports.selectColors = exports.overlayTokens = exports.textTokens = exports.surfaceTokens = exports.controlTokens = exports.disabledColors = exports.intentColors = exports.colorSchemes = exports.darkColors = exports.lightColors = void 0;
4
- exports.Tooltip = exports.Skeleton = exports.Popover = exports.Avatar = exports.Divider = exports.Chip = void 0;
3
+ exports.Input = exports.Badge = exports.Card = exports.Toggle = exports.Alert = exports.Checkbox = exports.ListDivider = exports.ListSubheader = exports.ListItem = exports.List = exports.SelectDropdown = exports.SelectTag = exports.SelectSeparator = exports.SelectGroup = exports.SelectOption = exports.Select = exports.DialogContent = exports.Dialog = exports.Icon = exports.Button = exports.themes = exports.useTheme = exports.ThemeProvider = exports.iconSize = exports.caption = exports.display = exports.heading = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = exports.skeletonColors = exports.avatarColors = exports.radioColors = exports.toggleColors = exports.checkboxColors = exports.listColors = exports.errorTokens = exports.tagTokens = exports.selectColors = exports.overlayTokens = exports.textTokens = exports.surfaceTokens = exports.controlTokens = exports.disabledColors = exports.intentColors = exports.colorSchemes = exports.darkColors = exports.lightColors = void 0;
4
+ exports.Text = exports.Tooltip = exports.Skeleton = exports.Popover = exports.Avatar = exports.Divider = exports.Chip = exports.Toast = exports.RadioGroup = exports.Radio = void 0;
5
5
  // Cast UI — Cross-platform design system component library
6
6
  //
7
7
  // Tokens
@@ -29,7 +29,10 @@ Object.defineProperty(exports, "fontWeight", { enumerable: true, get: function (
29
29
  Object.defineProperty(exports, "label", { enumerable: true, get: function () { return tokens_1.label; } });
30
30
  Object.defineProperty(exports, "title", { enumerable: true, get: function () { return tokens_1.title; } });
31
31
  Object.defineProperty(exports, "body", { enumerable: true, get: function () { return tokens_1.body; } });
32
+ Object.defineProperty(exports, "heading", { enumerable: true, get: function () { return tokens_1.heading; } });
33
+ Object.defineProperty(exports, "display", { enumerable: true, get: function () { return tokens_1.display; } });
32
34
  Object.defineProperty(exports, "caption", { enumerable: true, get: function () { return tokens_1.caption; } });
35
+ Object.defineProperty(exports, "iconSize", { enumerable: true, get: function () { return tokens_1.iconSize; } });
33
36
  // Theme
34
37
  var theme_1 = require("./theme");
35
38
  Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return theme_1.ThemeProvider; } });
@@ -84,3 +87,5 @@ var Skeleton_1 = require("./components/Skeleton");
84
87
  Object.defineProperty(exports, "Skeleton", { enumerable: true, get: function () { return Skeleton_1.Skeleton; } });
85
88
  var Tooltip_1 = require("./components/Tooltip");
86
89
  Object.defineProperty(exports, "Tooltip", { enumerable: true, get: function () { return Tooltip_1.Tooltip; } });
90
+ var Text_1 = require("./components/Text");
91
+ Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return Text_1.Text; } });
@@ -43,9 +43,9 @@ exports.themes = {
43
43
  },
44
44
  checkbox: {
45
45
  gap: 4, borderRadius: 4, focusRingWidth: 2,
46
- small: { indicatorSize: 16, iconSize: 10 },
47
- default: { indicatorSize: 20, iconSize: 12 },
48
- large: { indicatorSize: 24, iconSize: 14 },
46
+ small: { indicatorSize: 16, iconSize: 16 },
47
+ default: { indicatorSize: 20, iconSize: 20 },
48
+ large: { indicatorSize: 24, iconSize: 24 },
49
49
  },
50
50
  alert: {
51
51
  borderRadius: 8,
@@ -137,9 +137,9 @@ exports.themes = {
137
137
  },
138
138
  checkbox: {
139
139
  gap: 8, borderRadius: 4, focusRingWidth: 2,
140
- small: { indicatorSize: 16, iconSize: 10 },
141
- default: { indicatorSize: 20, iconSize: 12 },
142
- large: { indicatorSize: 24, iconSize: 14 },
140
+ small: { indicatorSize: 16, iconSize: 16 },
141
+ default: { indicatorSize: 20, iconSize: 20 },
142
+ large: { indicatorSize: 24, iconSize: 24 },
143
143
  },
144
144
  alert: {
145
145
  borderRadius: 8,
@@ -231,9 +231,9 @@ exports.themes = {
231
231
  },
232
232
  checkbox: {
233
233
  gap: 12, borderRadius: 4, focusRingWidth: 2,
234
- small: { indicatorSize: 16, iconSize: 10 },
235
- default: { indicatorSize: 20, iconSize: 12 },
236
- large: { indicatorSize: 24, iconSize: 14 },
234
+ small: { indicatorSize: 16, iconSize: 16 },
235
+ default: { indicatorSize: 20, iconSize: 20 },
236
+ large: { indicatorSize: 24, iconSize: 24 },
237
237
  },
238
238
  alert: {
239
239
  borderRadius: 8,
@@ -79,6 +79,8 @@ export type ColorScheme = {
79
79
  };
80
80
  /** Semantic text tokens */
81
81
  text: {
82
+ /** Default foreground for standalone text — text/primary (Text component default) */
83
+ primary: string;
82
84
  description: string;
83
85
  /** Placeholder text in form fields — intent/neutral/placeholder */
84
86
  placeholder: string;
@@ -237,6 +239,8 @@ export declare const surfaceTokens: {
237
239
  };
238
240
  /** Semantic text tokens */
239
241
  export declare const textTokens: {
242
+ /** Default foreground for standalone text — text/primary (Text component default) */
243
+ primary: string;
240
244
  description: string;
241
245
  /** Placeholder text in form fields — intent/neutral/placeholder */
242
246
  placeholder: string;
@@ -81,6 +81,7 @@ exports.lightColors = {
81
81
  overlay: { bg: '#FFFFFF', border: '#E5E7EB', borderRadius: 8 },
82
82
  },
83
83
  text: {
84
+ primary: '#374151', // cool-grey/700
84
85
  description: '#6B7280',
85
86
  placeholder: '#9CA3AF',
86
87
  },
@@ -221,6 +222,7 @@ exports.darkColors = {
221
222
  overlay: { bg: '#1F2937', border: '#374151', borderRadius: 8 },
222
223
  },
223
224
  text: {
225
+ primary: '#E5E7EB', // cool-grey/200
224
226
  description: '#9CA3AF',
225
227
  placeholder: '#6B7280',
226
228
  },
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Icon size scale — mirrors the Figma `icon/{xs,small,default,large}/size`
3
+ * variables (component collection, aliasing the primitive size scale).
4
+ * Standalone/decorative icons use this named scale; control-embedded icons are
5
+ * sized by their host component's density tokens.
6
+ */
7
+ export type IconSize = 'xs' | 'small' | 'default' | 'large';
8
+ export declare const iconSize: Record<IconSize, number>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Icon size scale — mirrors the Figma `icon/{xs,small,default,large}/size`
4
+ * variables (component collection, aliasing the primitive size scale).
5
+ * Standalone/decorative icons use this named scale; control-embedded icons are
6
+ * sized by their host component's density tokens.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.iconSize = void 0;
10
+ exports.iconSize = {
11
+ xs: 12,
12
+ small: 16,
13
+ default: 20,
14
+ large: 24,
15
+ };
@@ -1,2 +1,3 @@
1
1
  export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, radioColors, avatarColors, skeletonColors, type IntentName, type ProminenceName, type StateName, type ColorMode, type ColorScheme, } from './colors';
2
- export { fontFamily, fontWeight, label, title, body, caption, type LabelSize } from './typography';
2
+ export { fontFamily, fontWeight, label, title, body, heading, display, caption, type LabelSize, } from './typography';
3
+ export { iconSize, type IconSize } from './icon';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.caption = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = exports.skeletonColors = exports.avatarColors = exports.radioColors = exports.toggleColors = exports.checkboxColors = exports.listColors = exports.errorTokens = exports.tagTokens = exports.selectColors = exports.overlayTokens = exports.textTokens = exports.surfaceTokens = exports.controlTokens = exports.disabledColors = exports.intentColors = exports.colorSchemes = exports.darkColors = exports.lightColors = void 0;
3
+ exports.iconSize = exports.caption = exports.display = exports.heading = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = exports.skeletonColors = exports.avatarColors = exports.radioColors = exports.toggleColors = exports.checkboxColors = exports.listColors = exports.errorTokens = exports.tagTokens = exports.selectColors = exports.overlayTokens = exports.textTokens = exports.surfaceTokens = exports.controlTokens = exports.disabledColors = exports.intentColors = exports.colorSchemes = exports.darkColors = exports.lightColors = void 0;
4
4
  var colors_1 = require("./colors");
5
5
  Object.defineProperty(exports, "lightColors", { enumerable: true, get: function () { return colors_1.lightColors; } });
6
6
  Object.defineProperty(exports, "darkColors", { enumerable: true, get: function () { return colors_1.darkColors; } });
@@ -26,4 +26,8 @@ Object.defineProperty(exports, "fontWeight", { enumerable: true, get: function (
26
26
  Object.defineProperty(exports, "label", { enumerable: true, get: function () { return typography_1.label; } });
27
27
  Object.defineProperty(exports, "title", { enumerable: true, get: function () { return typography_1.title; } });
28
28
  Object.defineProperty(exports, "body", { enumerable: true, get: function () { return typography_1.body; } });
29
+ Object.defineProperty(exports, "heading", { enumerable: true, get: function () { return typography_1.heading; } });
30
+ Object.defineProperty(exports, "display", { enumerable: true, get: function () { return typography_1.display; } });
29
31
  Object.defineProperty(exports, "caption", { enumerable: true, get: function () { return typography_1.caption; } });
32
+ var icon_1 = require("./icon");
33
+ Object.defineProperty(exports, "iconSize", { enumerable: true, get: function () { return icon_1.iconSize; } });
@@ -27,6 +27,10 @@ type TypographyScale = Record<'sm' | 'md' | 'lg', {
27
27
  export declare const label: TypographyScale;
28
28
  export declare const title: TypographyScale;
29
29
  export declare const body: TypographyScale;
30
+ /** Heading scale — heading/{sm|md|lg}, rendered semibold */
31
+ export declare const heading: TypographyScale;
32
+ /** Display scale — display/{sm|md|lg}, hero/marketing sizes, rendered regular */
33
+ export declare const display: TypographyScale;
30
34
  /** Caption scale — helper text, group labels, tags */
31
35
  export declare const caption: {
32
36
  readonly fontSize: 11;
@@ -8,7 +8,7 @@
8
8
  * label/{sm|md|lg}, title/{sm|md|lg}, body/{sm|md|lg} → fontSize, lineHeight, letterSpacing
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.caption = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = void 0;
11
+ exports.caption = exports.display = exports.heading = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = void 0;
12
12
  const react_native_1 = require("react-native");
13
13
  exports.fontFamily = {
14
14
  sans: react_native_1.Platform.select({ web: 'Inter, system-ui, sans-serif', default: 'Inter' }),
@@ -40,5 +40,17 @@ exports.body = {
40
40
  md: { fontSize: 14, lineHeight: 20, letterSpacing: 0 },
41
41
  lg: { fontSize: 16, lineHeight: 24, letterSpacing: 0 },
42
42
  };
43
+ /** Heading scale — heading/{sm|md|lg}, rendered semibold */
44
+ exports.heading = {
45
+ sm: { fontSize: 24, lineHeight: 32, letterSpacing: 0 },
46
+ md: { fontSize: 30, lineHeight: 36, letterSpacing: -0.25 },
47
+ lg: { fontSize: 36, lineHeight: 40, letterSpacing: -0.25 },
48
+ };
49
+ /** Display scale — display/{sm|md|lg}, hero/marketing sizes, rendered regular */
50
+ exports.display = {
51
+ sm: { fontSize: 48, lineHeight: 56, letterSpacing: -0.25 },
52
+ md: { fontSize: 60, lineHeight: 64, letterSpacing: -0.5 },
53
+ lg: { fontSize: 72, lineHeight: 80, letterSpacing: -0.5 },
54
+ };
43
55
  /** Caption scale — helper text, group labels, tags */
44
56
  exports.caption = { fontSize: 11, lineHeight: 16, letterSpacing: 0.5 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@castui/cast-ui",
3
- "version": "4.2.2",
3
+ "version": "4.4.0",
4
4
  "description": "A cross-platform design system for React Native (iOS, Android, Web) with multi-theme support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",