@castui/cast-ui 4.6.0 → 4.7.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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Spinner — indeterminate circular loading indicator.
3
+ *
4
+ * Maps 1:1 to the Figma <Spinner> component:
5
+ * intent → neutral | brand | danger (arc colour)
6
+ * size → small | default | large (diameter + ring stroke)
7
+ *
8
+ * A Spinner is always indeterminate: it shows that work is happening, not how
9
+ * much is left. For a known percentage use <Progress> instead.
10
+ *
11
+ * Geometry. The diameter and ring stroke are the two tokens this component
12
+ * introduces (`spinner/{size}/diameter`, `spinner/{size}/stroke`). Both are
13
+ * keyed by the `size` prop and constant across the three densities, like
14
+ * <Progress> track-height. So Spinner reads no density-varying spacing. The
15
+ * ring radius is half the diameter, computed here.
16
+ *
17
+ * The ring is drawn with borders: all four sides take the faint track colour,
18
+ * then the top border takes the intent colour to form one visible arc. The
19
+ * whole ring rotates, so the arc sweeps around. This needs no SVG and keeps the
20
+ * zero-dependency contract.
21
+ *
22
+ * Colours. The arc binds to the intent system
23
+ * (`colors[intent].bold.default.bg`) so it tracks the host intent and any
24
+ * ThemeProvider colour overrides. The track ring is the dedicated
25
+ * `control/spinner/track/bg` semantic (cool-grey/200 light, cool-grey/700
26
+ * dark, matching the <Progress> track), mirrored as `scheme.spinner.track`, so
27
+ * it follows light/dark colour mode.
28
+ */
29
+ import { type StyleProp, type ViewStyle } from 'react-native';
30
+ import type { IntentName } from '../../tokens';
31
+ export type SpinnerSize = 'small' | 'default' | 'large';
32
+ export type SpinnerProps = {
33
+ /** Semantic intent — drives the arc colour. */
34
+ intent?: IntentName;
35
+ /** Size variant — controls diameter and ring stroke. */
36
+ size?: SpinnerSize;
37
+ /** Outer style — use for positioning (margin, alignSelf). */
38
+ style?: StyleProp<ViewStyle>;
39
+ /** Accessibility label — describes what is loading. */
40
+ accessibilityLabel?: string;
41
+ };
42
+ export declare function Spinner({ intent, size, style, accessibilityLabel, }: SpinnerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Spinner = Spinner;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * Spinner — indeterminate circular loading indicator.
7
+ *
8
+ * Maps 1:1 to the Figma <Spinner> component:
9
+ * intent → neutral | brand | danger (arc colour)
10
+ * size → small | default | large (diameter + ring stroke)
11
+ *
12
+ * A Spinner is always indeterminate: it shows that work is happening, not how
13
+ * much is left. For a known percentage use <Progress> instead.
14
+ *
15
+ * Geometry. The diameter and ring stroke are the two tokens this component
16
+ * introduces (`spinner/{size}/diameter`, `spinner/{size}/stroke`). Both are
17
+ * keyed by the `size` prop and constant across the three densities, like
18
+ * <Progress> track-height. So Spinner reads no density-varying spacing. The
19
+ * ring radius is half the diameter, computed here.
20
+ *
21
+ * The ring is drawn with borders: all four sides take the faint track colour,
22
+ * then the top border takes the intent colour to form one visible arc. The
23
+ * whole ring rotates, so the arc sweeps around. This needs no SVG and keeps the
24
+ * zero-dependency contract.
25
+ *
26
+ * Colours. The arc binds to the intent system
27
+ * (`colors[intent].bold.default.bg`) so it tracks the host intent and any
28
+ * ThemeProvider colour overrides. The track ring is the dedicated
29
+ * `control/spinner/track/bg` semantic (cool-grey/200 light, cool-grey/700
30
+ * dark, matching the <Progress> track), mirrored as `scheme.spinner.track`, so
31
+ * it follows light/dark colour mode.
32
+ */
33
+ const react_1 = require("react");
34
+ const react_native_1 = require("react-native");
35
+ const theme_1 = require("../../theme");
36
+ // ---------------------------------------------------------------------------
37
+ // Constants
38
+ // ---------------------------------------------------------------------------
39
+ /** One full rotation, in milliseconds. */
40
+ const ROTATION_DURATION = 800;
41
+ // ---------------------------------------------------------------------------
42
+ // Component
43
+ // ---------------------------------------------------------------------------
44
+ function Spinner({ intent = 'brand', size = 'default', style, accessibilityLabel = 'Loading', }) {
45
+ const { components, colors, scheme } = (0, theme_1.useTheme)();
46
+ const { diameter, stroke } = components.spinner[size];
47
+ const arc = colors[intent].bold.default.bg;
48
+ const trackColor = scheme.spinner.track;
49
+ const spin = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
50
+ (0, react_1.useEffect)(() => {
51
+ spin.setValue(0);
52
+ const loop = react_native_1.Animated.loop(react_native_1.Animated.timing(spin, {
53
+ toValue: 1,
54
+ duration: ROTATION_DURATION,
55
+ easing: react_native_1.Easing.linear,
56
+ useNativeDriver: true,
57
+ }));
58
+ loop.start();
59
+ return () => loop.stop();
60
+ }, [spin]);
61
+ const rotate = spin.interpolate({
62
+ inputRange: [0, 1],
63
+ outputRange: ['0deg', '360deg'],
64
+ });
65
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { accessibilityRole: "progressbar", accessibilityLabel: accessibilityLabel, accessibilityState: { busy: true }, style: [
66
+ {
67
+ width: diameter,
68
+ height: diameter,
69
+ borderRadius: diameter / 2,
70
+ borderWidth: stroke,
71
+ borderColor: trackColor,
72
+ borderTopColor: arc,
73
+ transform: [{ rotate }],
74
+ },
75
+ style,
76
+ ] }));
77
+ }
@@ -0,0 +1 @@
1
+ export { Spinner, type SpinnerProps, type SpinnerSize } from './Spinner';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Spinner = void 0;
4
+ var Spinner_1 = require("./Spinner");
5
+ Object.defineProperty(exports, "Spinner", { enumerable: true, get: function () { return Spinner_1.Spinner; } });
@@ -26,7 +26,7 @@
26
26
  * `components.tabs`; `indicatorHeight` is keyed by `size` and constant across
27
27
  * density (like Progress's track-height); `indicatorRadius` is the pill radius.
28
28
  * Colours: the selected indicator and selected label bind to the intent system
29
- * (`colors[intent].bold.default.bg`); unselected labels use
29
+ * (`colors[intent].default.default.fg`); unselected labels use
30
30
  * `scheme.text.description`, hovered use `scheme.text.primary`, disabled use
31
31
  * `scheme.disabled.fg`; the baseline divider is the dedicated
32
32
  * `scheme.tabs.track` semantic (cool-grey/200 light, cool-grey/700 dark).
@@ -31,7 +31,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
31
31
  * `components.tabs`; `indicatorHeight` is keyed by `size` and constant across
32
32
  * density (like Progress's track-height); `indicatorRadius` is the pill radius.
33
33
  * Colours: the selected indicator and selected label bind to the intent system
34
- * (`colors[intent].bold.default.bg`); unselected labels use
34
+ * (`colors[intent].default.default.fg`); unselected labels use
35
35
  * `scheme.text.description`, hovered use `scheme.text.primary`, disabled use
36
36
  * `scheme.disabled.fg`; the baseline divider is the dedicated
37
37
  * `scheme.tabs.track` semantic (cool-grey/200 light, cool-grey/700 dark).
@@ -69,7 +69,10 @@ function Tab({ value, children, leadingIcon, disabled = false, style, accessibil
69
69
  const sizeTokens = components.tabs[size];
70
70
  const { indicatorRadius } = components.tabs;
71
71
  const isSelected = selectedValue === value;
72
- const accent = colors[intent].bold.default.bg;
72
+ // Selected label + indicator track the intent *fg* (text/line colour), mirroring
73
+ // the Figma binding intent/{intent}/default/default/fg — same hex as bold/bg in
74
+ // light, but correctly the text colour (not the solid fill) in dark mode.
75
+ const accent = colors[intent].default.default.fg;
73
76
  // Resolve the label/icon colour from interaction + selection state.
74
77
  const fg = disabled
75
78
  ? scheme.disabled.fg
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, progressColors, tabsColors, 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
- 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 ProgressSizeTokens, type ProgressThemeTokens, type TabsSizeTokens, type TabsThemeTokens, type DeepPartial, } from './theme';
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 ProgressSizeTokens, type ProgressThemeTokens, type TabsSizeTokens, type TabsThemeTokens, type SpinnerSizeTokens, type SpinnerThemeTokens, type DeepPartial, } from './theme';
3
3
  export { Button, type ButtonProps, type ButtonSize } from './components/Button';
4
4
  export { Icon, type IconProps } from './components/Icon';
5
5
  export { Dialog, DialogContent, type DialogProps, type DialogContentProps, type DialogAction, type DialogSize, } from './components/Dialog';
@@ -21,4 +21,5 @@ export { Skeleton, type SkeletonProps, type SkeletonShape, } from './components/
21
21
  export { Tooltip, type TooltipProps, type TooltipSize, type TooltipDirection, } from './components/Tooltip';
22
22
  export { Text, type TextProps, type TextType } from './components/Text';
23
23
  export { Progress, type ProgressProps, type ProgressSize } from './components/Progress';
24
+ export { Spinner, type SpinnerProps, type SpinnerSize } from './components/Spinner';
24
25
  export { Tabs, Tab, type TabsProps, type TabProps, type TabsSize, } from './components/Tabs';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  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.tabsColors = exports.progressColors = 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.Tab = exports.Tabs = exports.Progress = exports.Text = exports.Tooltip = exports.Skeleton = exports.Popover = exports.Avatar = exports.Divider = exports.Chip = exports.Toast = exports.RadioGroup = exports.Radio = exports.Input = exports.Badge = void 0;
4
+ exports.Tab = exports.Tabs = exports.Spinner = exports.Progress = exports.Text = exports.Tooltip = exports.Skeleton = exports.Popover = exports.Avatar = exports.Divider = exports.Chip = exports.Toast = exports.RadioGroup = exports.Radio = exports.Input = exports.Badge = void 0;
5
5
  // Cast UI — Cross-platform design system component library
6
6
  //
7
7
  // Tokens
@@ -93,6 +93,8 @@ var Text_1 = require("./components/Text");
93
93
  Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return Text_1.Text; } });
94
94
  var Progress_1 = require("./components/Progress");
95
95
  Object.defineProperty(exports, "Progress", { enumerable: true, get: function () { return Progress_1.Progress; } });
96
+ var Spinner_1 = require("./components/Spinner");
97
+ Object.defineProperty(exports, "Spinner", { enumerable: true, get: function () { return Spinner_1.Spinner; } });
96
98
  var Tabs_1 = require("./components/Tabs");
97
99
  Object.defineProperty(exports, "Tabs", { enumerable: true, get: function () { return Tabs_1.Tabs; } });
98
100
  Object.defineProperty(exports, "Tab", { enumerable: true, get: function () { return Tabs_1.Tab; } });
@@ -1,3 +1,3 @@
1
1
  export { ThemeProvider, useTheme, type ThemeProviderProps, type Theme } from './ThemeContext';
2
2
  export { themes } from './themes';
3
- export type { DensityTheme, ComponentTokens, ButtonSizeTokens, ButtonThemeTokens, DialogSizeTokens, DialogThemeTokens, InputSizeTokens, InputThemeTokens, SelectContentTokens, SelectOptionTokens, SelectGroupTokens, SelectSeparatorTokens, SelectThemeTokens, ListItemTokens, ListSubheaderTokens, ListThemeTokens, CheckboxSizeTokens, CheckboxThemeTokens, AlertSizeTokens, AlertThemeTokens, ToggleSizeTokens, ToggleThemeTokens, CardSizeTokens, CardThemeTokens, BadgeSizeTokens, BadgeThemeTokens, RadioSizeTokens, RadioThemeTokens, ToastSizeTokens, ToastThemeTokens, ChipSizeTokens, ChipThemeTokens, AvatarSizeTokens, AvatarThemeTokens, PopoverSizeTokens, PopoverThemeTokens, TooltipSizeTokens, TooltipThemeTokens, ProgressSizeTokens, ProgressThemeTokens, TabsSizeTokens, TabsThemeTokens, DeepPartial, } from './types';
3
+ export type { DensityTheme, ComponentTokens, ButtonSizeTokens, ButtonThemeTokens, DialogSizeTokens, DialogThemeTokens, InputSizeTokens, InputThemeTokens, SelectContentTokens, SelectOptionTokens, SelectGroupTokens, SelectSeparatorTokens, SelectThemeTokens, ListItemTokens, ListSubheaderTokens, ListThemeTokens, CheckboxSizeTokens, CheckboxThemeTokens, AlertSizeTokens, AlertThemeTokens, ToggleSizeTokens, ToggleThemeTokens, CardSizeTokens, CardThemeTokens, BadgeSizeTokens, BadgeThemeTokens, RadioSizeTokens, RadioThemeTokens, ToastSizeTokens, ToastThemeTokens, ChipSizeTokens, ChipThemeTokens, AvatarSizeTokens, AvatarThemeTokens, PopoverSizeTokens, PopoverThemeTokens, TooltipSizeTokens, TooltipThemeTokens, ProgressSizeTokens, ProgressThemeTokens, TabsSizeTokens, TabsThemeTokens, SpinnerSizeTokens, SpinnerThemeTokens, DeepPartial, } from './types';
@@ -112,6 +112,11 @@ exports.themes = {
112
112
  default: { trackHeight: 8 },
113
113
  large: { trackHeight: 12 },
114
114
  },
115
+ spinner: {
116
+ small: { diameter: 16, stroke: 2 },
117
+ default: { diameter: 24, stroke: 2 },
118
+ large: { diameter: 32, stroke: 4 },
119
+ },
115
120
  tabs: {
116
121
  listGap: 8, indicatorRadius: 9999,
117
122
  small: { gap: 4, paddingX: 8, paddingY: 4, indicatorHeight: 2 },
@@ -218,6 +223,11 @@ exports.themes = {
218
223
  default: { trackHeight: 8 },
219
224
  large: { trackHeight: 12 },
220
225
  },
226
+ spinner: {
227
+ small: { diameter: 16, stroke: 2 },
228
+ default: { diameter: 24, stroke: 2 },
229
+ large: { diameter: 32, stroke: 4 },
230
+ },
221
231
  tabs: {
222
232
  listGap: 16, indicatorRadius: 9999,
223
233
  small: { gap: 6, paddingX: 10, paddingY: 6, indicatorHeight: 2 },
@@ -324,6 +334,11 @@ exports.themes = {
324
334
  default: { trackHeight: 8 },
325
335
  large: { trackHeight: 12 },
326
336
  },
337
+ spinner: {
338
+ small: { diameter: 16, stroke: 2 },
339
+ default: { diameter: 24, stroke: 2 },
340
+ large: { diameter: 32, stroke: 4 },
341
+ },
327
342
  tabs: {
328
343
  listGap: 24, indicatorRadius: 9999,
329
344
  small: { gap: 8, paddingX: 12, paddingY: 8, indicatorHeight: 2 },
@@ -274,6 +274,20 @@ export type TabsThemeTokens = {
274
274
  default: TabsSizeTokens;
275
275
  large: TabsSizeTokens;
276
276
  };
277
+ /** Spinner geometry for one size variant. Both values are keyed by the `size`
278
+ * prop and constant across density (like Progress track-height / Tabs
279
+ * indicator-height — bound to a primitive `size/*`). */
280
+ export type SpinnerSizeTokens = {
281
+ diameter: number;
282
+ stroke: number;
283
+ };
284
+ /** Spinner tokens — diameter + ring stroke vary by the `size` prop and are
285
+ * constant across density. No density-varying spacing. */
286
+ export type SpinnerThemeTokens = {
287
+ small: SpinnerSizeTokens;
288
+ default: SpinnerSizeTokens;
289
+ large: SpinnerSizeTokens;
290
+ };
277
291
  /**
278
292
  * Component-level tokens that vary by density theme.
279
293
  * Extended as new components are added to the library.
@@ -297,6 +311,7 @@ export type ComponentTokens = {
297
311
  tooltip: TooltipThemeTokens;
298
312
  progress: ProgressThemeTokens;
299
313
  tabs: TabsThemeTokens;
314
+ spinner: SpinnerThemeTokens;
300
315
  };
301
316
  /** Utility type for partial overrides at any depth */
302
317
  export type DeepPartial<T> = {
@@ -200,6 +200,10 @@ export type ColorScheme = {
200
200
  tabs: {
201
201
  track: string;
202
202
  };
203
+ /** Spinner colours — track ring background (the arc uses the intent system) */
204
+ spinner: {
205
+ track: string;
206
+ };
203
207
  /** Avatar colours — initials/icon fallback surface + foreground */
204
208
  avatar: {
205
209
  bg: string;
@@ -141,6 +141,7 @@ exports.lightColors = {
141
141
  },
142
142
  progress: { track: '#E5E7EB' }, // control/progress/track/bg → cool-grey/200
143
143
  tabs: { track: '#E5E7EB' }, // control/tabs/track/bg → cool-grey/200
144
+ spinner: { track: '#E5E7EB' }, // control/spinner/track/bg → cool-grey/200
144
145
  avatar: { bg: '#F3F4F6', fg: '#374151' },
145
146
  skeleton: { bg: '#F3F4F6', highlight: '#E5E7EB' },
146
147
  list: {
@@ -284,6 +285,7 @@ exports.darkColors = {
284
285
  },
285
286
  progress: { track: '#374151' }, // control/progress/track/bg → cool-grey/700
286
287
  tabs: { track: '#374151' }, // control/tabs/track/bg → cool-grey/700
288
+ spinner: { track: '#374151' }, // control/spinner/track/bg → cool-grey/700
287
289
  avatar: { bg: '#374151', fg: '#E5E7EB' },
288
290
  skeleton: { bg: '#1F2937', highlight: '#374151' },
289
291
  list: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@castui/cast-ui",
3
- "version": "4.6.0",
3
+ "version": "4.7.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",