@castui/cast-ui 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +61 -0
  2. package/dist/components/Accordion/Accordion.d.ts +80 -0
  3. package/dist/components/Accordion/Accordion.js +157 -0
  4. package/dist/components/Accordion/index.d.ts +1 -0
  5. package/dist/components/Accordion/index.js +6 -0
  6. package/dist/components/AppBar/AppBar.d.ts +47 -0
  7. package/dist/components/AppBar/AppBar.js +47 -0
  8. package/dist/components/AppBar/index.d.ts +1 -0
  9. package/dist/components/AppBar/index.js +5 -0
  10. package/dist/components/Autocomplete/Autocomplete.d.ts +70 -0
  11. package/dist/components/Autocomplete/Autocomplete.js +249 -0
  12. package/dist/components/Autocomplete/index.d.ts +1 -0
  13. package/dist/components/Autocomplete/index.js +5 -0
  14. package/dist/components/Backdrop/Backdrop.d.ts +32 -0
  15. package/dist/components/Backdrop/Backdrop.js +74 -0
  16. package/dist/components/Backdrop/index.d.ts +1 -0
  17. package/dist/components/Backdrop/index.js +5 -0
  18. package/dist/components/BottomSheet/BottomSheet.d.ts +50 -0
  19. package/dist/components/BottomSheet/BottomSheet.js +159 -0
  20. package/dist/components/BottomSheet/index.d.ts +1 -0
  21. package/dist/components/BottomSheet/index.js +6 -0
  22. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +63 -0
  23. package/dist/components/Breadcrumbs/Breadcrumbs.js +143 -0
  24. package/dist/components/Breadcrumbs/index.d.ts +1 -0
  25. package/dist/components/Breadcrumbs/index.js +6 -0
  26. package/dist/components/CodeBlock/CodeBlock.d.ts +42 -0
  27. package/dist/components/CodeBlock/CodeBlock.js +110 -0
  28. package/dist/components/CodeBlock/index.d.ts +1 -0
  29. package/dist/components/CodeBlock/index.js +5 -0
  30. package/dist/components/Drawer/Drawer.d.ts +51 -0
  31. package/dist/components/Drawer/Drawer.js +168 -0
  32. package/dist/components/Drawer/index.d.ts +1 -0
  33. package/dist/components/Drawer/index.js +6 -0
  34. package/dist/components/Link/Link.d.ts +51 -0
  35. package/dist/components/Link/Link.js +73 -0
  36. package/dist/components/Link/index.d.ts +1 -0
  37. package/dist/components/Link/index.js +5 -0
  38. package/dist/components/Menu/Menu.d.ts +91 -0
  39. package/dist/components/Menu/Menu.js +211 -0
  40. package/dist/components/Menu/index.d.ts +1 -0
  41. package/dist/components/Menu/index.js +9 -0
  42. package/dist/components/Slider/Slider.d.ts +47 -0
  43. package/dist/components/Slider/Slider.js +132 -0
  44. package/dist/components/Slider/index.d.ts +1 -0
  45. package/dist/components/Slider/index.js +5 -0
  46. package/dist/components/SpeedDial/SpeedDial.d.ts +72 -0
  47. package/dist/components/SpeedDial/SpeedDial.js +189 -0
  48. package/dist/components/SpeedDial/index.d.ts +1 -0
  49. package/dist/components/SpeedDial/index.js +6 -0
  50. package/dist/components/Table/Table.d.ts +74 -0
  51. package/dist/components/Table/Table.js +176 -0
  52. package/dist/components/Table/index.d.ts +1 -0
  53. package/dist/components/Table/index.js +9 -0
  54. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.d.ts +69 -0
  55. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.js +158 -0
  56. package/dist/components/ToggleButtonGroup/index.d.ts +1 -0
  57. package/dist/components/ToggleButtonGroup/index.js +6 -0
  58. package/dist/hooks/index.d.ts +1 -0
  59. package/dist/hooks/index.js +7 -0
  60. package/dist/hooks/useBreakpoint.d.ts +22 -0
  61. package/dist/hooks/useBreakpoint.js +45 -0
  62. package/dist/index.d.ts +17 -2
  63. package/dist/index.js +59 -2
  64. package/dist/theme/ThemeContext.d.ts +8 -1
  65. package/dist/theme/ThemeContext.js +7 -4
  66. package/dist/theme/applyCastTheme.d.ts +75 -0
  67. package/dist/theme/applyCastTheme.js +95 -0
  68. package/dist/theme/index.d.ts +2 -1
  69. package/dist/theme/index.js +3 -1
  70. package/dist/theme/themes.js +177 -0
  71. package/dist/theme/types.d.ts +177 -0
  72. package/dist/tokens/breakpoints.d.ts +57 -0
  73. package/dist/tokens/breakpoints.js +92 -0
  74. package/dist/tokens/colors.d.ts +44 -0
  75. package/dist/tokens/colors.js +47 -1
  76. package/dist/tokens/index.d.ts +2 -1
  77. package/dist/tokens/index.js +9 -1
  78. package/package.json +2 -1
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Link — an inline or standalone text link.
3
+ *
4
+ * Maps 1:1 to the Figma <Link> component:
5
+ * intent → neutral | brand | danger (link colour)
6
+ * prominence is fixed to the subtle scheme (coloured text, no fill)
7
+ * size → small | default | large (typography + icon size + gap)
8
+ * underline → none | hover | always
9
+ *
10
+ * Colours come from the intent system's subtle prominence fg
11
+ * (colors[intent].subtle.{state}.fg). Brand is the default, so a link reads
12
+ * blue by default and the darker blue on hover; disabled uses the shared
13
+ * disabled fg. The gap between an icon and the label is the one spacing token
14
+ * (link/{size}/gap), density-varying. Typography is the label scale matched to
15
+ * size; the icon size follows the named Icon scale 1:1 (small→16, default→20,
16
+ * large→24), like Button.
17
+ *
18
+ * Links navigate. On web, passing `href` renders a real anchor through
19
+ * react-native-web; on native it is informational and `onPress` drives
20
+ * navigation. Fonts are consumer-loaded (Inter).
21
+ */
22
+ import React from 'react';
23
+ import { type ViewStyle, type StyleProp, type GestureResponderEvent } from 'react-native';
24
+ import type { IntentName } from '../../tokens';
25
+ export type LinkSize = 'small' | 'default' | 'large';
26
+ export type LinkUnderline = 'none' | 'hover' | 'always';
27
+ export type LinkProps = {
28
+ /** The link text. */
29
+ children: string;
30
+ /** Semantic intent — drives the link colour. Defaults to brand. */
31
+ intent?: IntentName;
32
+ /** Size variant — controls typography scale, icon size, and gap. */
33
+ size?: LinkSize;
34
+ /** When the underline shows. Defaults to "hover". */
35
+ underline?: LinkUnderline;
36
+ /** Disables interaction and applies muted styling. */
37
+ disabled?: boolean;
38
+ /** Icon before the label — Material Symbols name string or a ReactNode. */
39
+ leadingIcon?: string | React.ReactNode;
40
+ /** Icon after the label — Material Symbols name string or a ReactNode. */
41
+ trailingIcon?: string | React.ReactNode;
42
+ /** Destination URL. Renders a real anchor on web; informational on native. */
43
+ href?: string;
44
+ /** Press handler. */
45
+ onPress?: (e: GestureResponderEvent) => void;
46
+ /** Outer style — use for positioning (margin, flex, alignSelf). */
47
+ style?: StyleProp<ViewStyle>;
48
+ /** Accessibility label — falls back to the link text. */
49
+ accessibilityLabel?: string;
50
+ };
51
+ export declare function Link({ children, intent, size, underline, disabled, leadingIcon, trailingIcon, href, onPress, style, accessibilityLabel, }: LinkProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Link = Link;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * Link — an inline or standalone text link.
7
+ *
8
+ * Maps 1:1 to the Figma <Link> component:
9
+ * intent → neutral | brand | danger (link colour)
10
+ * prominence is fixed to the subtle scheme (coloured text, no fill)
11
+ * size → small | default | large (typography + icon size + gap)
12
+ * underline → none | hover | always
13
+ *
14
+ * Colours come from the intent system's subtle prominence fg
15
+ * (colors[intent].subtle.{state}.fg). Brand is the default, so a link reads
16
+ * blue by default and the darker blue on hover; disabled uses the shared
17
+ * disabled fg. The gap between an icon and the label is the one spacing token
18
+ * (link/{size}/gap), density-varying. Typography is the label scale matched to
19
+ * size; the icon size follows the named Icon scale 1:1 (small→16, default→20,
20
+ * large→24), like Button.
21
+ *
22
+ * Links navigate. On web, passing `href` renders a real anchor through
23
+ * react-native-web; on native it is informational and `onPress` drives
24
+ * navigation. Fonts are consumer-loaded (Inter).
25
+ */
26
+ const react_1 = require("react");
27
+ const react_native_1 = require("react-native");
28
+ const theme_1 = require("../../theme");
29
+ const tokens_1 = require("../../tokens");
30
+ const Icon_1 = require("../Icon");
31
+ // ---------------------------------------------------------------------------
32
+ // Constants
33
+ // ---------------------------------------------------------------------------
34
+ /** Maps link size → label typography scale. */
35
+ const LABEL_SCALE = {
36
+ small: 'sm',
37
+ default: 'md',
38
+ large: 'lg',
39
+ };
40
+ // ---------------------------------------------------------------------------
41
+ // Component
42
+ // ---------------------------------------------------------------------------
43
+ function Link({ children, intent = 'brand', size = 'default', underline = 'hover', disabled = false, leadingIcon, trailingIcon, href, onPress, style, accessibilityLabel, }) {
44
+ const { components, colors, scheme } = (0, theme_1.useTheme)();
45
+ const [isHovered, setIsHovered] = (0, react_1.useState)(false);
46
+ const { gap } = components.link[size];
47
+ const labelTokens = tokens_1.label[LABEL_SCALE[size]];
48
+ const subtle = colors[intent].subtle;
49
+ const fg = disabled
50
+ ? scheme.disabled.fg
51
+ : isHovered
52
+ ? subtle.hover.fg
53
+ : subtle.default.fg;
54
+ const showUnderline = underline === 'always' || (underline === 'hover' && isHovered && !disabled);
55
+ const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: fg })) : (leadingIcon);
56
+ const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: size, color: fg })) : (trailingIcon);
57
+ // react-native-web forwards `href` to render an <a>; native ignores it.
58
+ const hrefProps = href ? { href } : {};
59
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { ...hrefProps, onPress: disabled ? undefined : onPress, disabled: disabled, onHoverIn: () => setIsHovered(true), onHoverOut: () => setIsHovered(false), accessibilityRole: "link", accessibilityLabel: accessibilityLabel || children, accessibilityState: { disabled }, style: style, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: {
60
+ flexDirection: 'row',
61
+ alignItems: 'center',
62
+ alignSelf: 'flex-start',
63
+ gap,
64
+ }, children: [resolvedLeading, (0, jsx_runtime_1.jsx)(react_native_1.Text, { selectable: false, style: {
65
+ fontFamily: tokens_1.fontFamily.sans,
66
+ fontWeight: tokens_1.fontWeight.medium,
67
+ fontSize: labelTokens.fontSize,
68
+ lineHeight: labelTokens.lineHeight,
69
+ letterSpacing: labelTokens.letterSpacing,
70
+ color: fg,
71
+ textDecorationLine: showUnderline ? 'underline' : 'none',
72
+ }, children: children }), resolvedTrailing] }) }));
73
+ }
@@ -0,0 +1 @@
1
+ export { Link, type LinkProps, type LinkSize, type LinkUnderline } from './Link';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Link = void 0;
4
+ var Link_1 = require("./Link");
5
+ Object.defineProperty(exports, "Link", { enumerable: true, get: function () { return Link_1.Link; } });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Menu — a transient overlay of actions, anchored to a trigger.
3
+ *
4
+ * Compound component. <Menu> owns the open state and the anchored overlay;
5
+ * <MenuItem> is one action row; <MenuDivider> separates groups; <MenuLabel> is
6
+ * a section heading.
7
+ *
8
+ * <Menu trigger={<Icon name="more_vert" />}>
9
+ * <MenuItem leadingIcon="edit" onPress={edit}>Edit</MenuItem>
10
+ * <MenuItem leadingIcon="content_copy" onPress={dupe}>Duplicate</MenuItem>
11
+ * <MenuDivider />
12
+ * <MenuItem leadingIcon="delete" intent="danger" onPress={remove}>Delete</MenuItem>
13
+ * </Menu>
14
+ *
15
+ * Maps 1:1 to the Figma <Menu> component:
16
+ * size → small | default | large (item typography + icon size)
17
+ * item state → default | hover | selected | disabled
18
+ *
19
+ * A Menu is not a Select. It fires actions; it does not hold a form value. So it
20
+ * has its own dedicated tokens (menu/*) and its own colour slice
21
+ * (scheme.menu.item), mirroring the select shape but namespaced to menu, so the
22
+ * Figma <Menu> set binds to menu variables, not select ones. Item spacing varies
23
+ * by density; the size prop drives typography (like Select). Surface reuses the
24
+ * shared overlay tokens. Fonts are consumer-loaded.
25
+ *
26
+ * Exports:
27
+ * Menu — trigger + anchored overlay
28
+ * MenuItem — one action row
29
+ * MenuDivider — a separator line
30
+ * MenuLabel — a section heading
31
+ * MenuContent — the floating card, for custom overlay/anchoring
32
+ */
33
+ import React from 'react';
34
+ import { type ViewStyle, type StyleProp, type GestureResponderEvent } from 'react-native';
35
+ import type { IntentName } from '../../tokens';
36
+ export type MenuSize = 'small' | 'default' | 'large';
37
+ export type MenuPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
38
+ export type MenuProps = {
39
+ /** The element that opens the menu. Menu owns the press. */
40
+ trigger: React.ReactNode;
41
+ /** Menu rows (MenuItem / MenuDivider / MenuLabel). */
42
+ children: React.ReactNode;
43
+ /** Size variant — item typography and icon size. */
44
+ size?: MenuSize;
45
+ /** Where the overlay opens relative to the trigger. */
46
+ placement?: MenuPlacement;
47
+ /** Controlled open state. Omit for uncontrolled. */
48
+ open?: boolean;
49
+ /** Open-state change handler. */
50
+ onOpenChange?: (open: boolean) => void;
51
+ /** Initial open state when uncontrolled. */
52
+ defaultOpen?: boolean;
53
+ /** Close the menu when an item is pressed. Defaults to true. */
54
+ closeOnSelect?: boolean;
55
+ /** Outer style for the anchor wrapper. */
56
+ style?: StyleProp<ViewStyle>;
57
+ /** Accessibility label for the trigger. */
58
+ accessibilityLabel?: string;
59
+ };
60
+ export type MenuItemProps = {
61
+ /** The item label. */
62
+ children: string;
63
+ /** Press handler. */
64
+ onPress?: (e: GestureResponderEvent) => void;
65
+ /** Leading icon — Material Symbols name string or a ReactNode. */
66
+ leadingIcon?: string | React.ReactNode;
67
+ /** Trailing icon — Material Symbols name string or a ReactNode. */
68
+ trailingIcon?: string | React.ReactNode;
69
+ /** Keyboard shortcut hint shown at the end of the row. */
70
+ shortcut?: string;
71
+ /** Marks the item as active. Shows the selected colours and a check. */
72
+ selected?: boolean;
73
+ /** Intent — neutral (default) or danger for destructive actions. */
74
+ intent?: Extract<IntentName, 'neutral' | 'danger'>;
75
+ /** Disables the item. */
76
+ disabled?: boolean;
77
+ /** Accessibility label — falls back to the label text. */
78
+ accessibilityLabel?: string;
79
+ };
80
+ export type MenuLabelProps = {
81
+ children: string;
82
+ };
83
+ export declare function MenuItem({ children, onPress, leadingIcon, trailingIcon, shortcut, selected, intent, disabled, accessibilityLabel, }: MenuItemProps): import("react/jsx-runtime").JSX.Element;
84
+ export declare function MenuLabel({ children }: MenuLabelProps): import("react/jsx-runtime").JSX.Element;
85
+ export declare function MenuDivider(): import("react/jsx-runtime").JSX.Element;
86
+ export type MenuContentProps = {
87
+ children: React.ReactNode;
88
+ style?: StyleProp<ViewStyle>;
89
+ };
90
+ export declare function MenuContent({ children, style }: MenuContentProps): import("react/jsx-runtime").JSX.Element;
91
+ export declare function Menu({ trigger, children, size, placement, open: controlledOpen, onOpenChange, defaultOpen, closeOnSelect, style, accessibilityLabel, }: MenuProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MenuItem = MenuItem;
4
+ exports.MenuLabel = MenuLabel;
5
+ exports.MenuDivider = MenuDivider;
6
+ exports.MenuContent = MenuContent;
7
+ exports.Menu = Menu;
8
+ const jsx_runtime_1 = require("react/jsx-runtime");
9
+ /**
10
+ * Menu — a transient overlay of actions, anchored to a trigger.
11
+ *
12
+ * Compound component. <Menu> owns the open state and the anchored overlay;
13
+ * <MenuItem> is one action row; <MenuDivider> separates groups; <MenuLabel> is
14
+ * a section heading.
15
+ *
16
+ * <Menu trigger={<Icon name="more_vert" />}>
17
+ * <MenuItem leadingIcon="edit" onPress={edit}>Edit</MenuItem>
18
+ * <MenuItem leadingIcon="content_copy" onPress={dupe}>Duplicate</MenuItem>
19
+ * <MenuDivider />
20
+ * <MenuItem leadingIcon="delete" intent="danger" onPress={remove}>Delete</MenuItem>
21
+ * </Menu>
22
+ *
23
+ * Maps 1:1 to the Figma <Menu> component:
24
+ * size → small | default | large (item typography + icon size)
25
+ * item state → default | hover | selected | disabled
26
+ *
27
+ * A Menu is not a Select. It fires actions; it does not hold a form value. So it
28
+ * has its own dedicated tokens (menu/*) and its own colour slice
29
+ * (scheme.menu.item), mirroring the select shape but namespaced to menu, so the
30
+ * Figma <Menu> set binds to menu variables, not select ones. Item spacing varies
31
+ * by density; the size prop drives typography (like Select). Surface reuses the
32
+ * shared overlay tokens. Fonts are consumer-loaded.
33
+ *
34
+ * Exports:
35
+ * Menu — trigger + anchored overlay
36
+ * MenuItem — one action row
37
+ * MenuDivider — a separator line
38
+ * MenuLabel — a section heading
39
+ * MenuContent — the floating card, for custom overlay/anchoring
40
+ */
41
+ const react_1 = require("react");
42
+ const react_native_1 = require("react-native");
43
+ const theme_1 = require("../../theme");
44
+ const tokens_1 = require("../../tokens");
45
+ const Text_1 = require("../Text");
46
+ const Icon_1 = require("../Icon");
47
+ const MenuCtx = (0, react_1.createContext)(null);
48
+ function useMenuContext(component) {
49
+ const ctx = (0, react_1.useContext)(MenuCtx);
50
+ if (!ctx)
51
+ throw new Error(`<${component}> must be used within <Menu>`);
52
+ return ctx;
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Constants
56
+ // ---------------------------------------------------------------------------
57
+ /** Maps menu size → label typography scale (Text component `type`). */
58
+ const LABEL_TYPE = {
59
+ small: 'label-sm',
60
+ default: 'label-md',
61
+ large: 'label-lg',
62
+ };
63
+ /** Minimum width of the floating card. Layout default, not a token. */
64
+ const MIN_WIDTH = 180;
65
+ /** The card never grows past this height before it scrolls. */
66
+ const CONTENT_MAX_HEIGHT = 320;
67
+ const SHADOW_WEB = {
68
+ boxShadow: '0px 2px 4px -2px rgba(0,0,0,0.05), 0px 4px 6px -1px rgba(0,0,0,0.07)',
69
+ };
70
+ const SHADOW_NATIVE = {
71
+ shadowColor: '#000000',
72
+ shadowOffset: { width: 0, height: 4 },
73
+ shadowOpacity: 0.07,
74
+ shadowRadius: 6,
75
+ elevation: 4,
76
+ };
77
+ // ---------------------------------------------------------------------------
78
+ // MenuItem
79
+ // ---------------------------------------------------------------------------
80
+ function MenuItem({ children, onPress, leadingIcon, trailingIcon, shortcut, selected = false, intent = 'neutral', disabled = false, accessibilityLabel, }) {
81
+ const { size, onClose, closeOnSelect } = useMenuContext('MenuItem');
82
+ const { components, colors, scheme } = (0, theme_1.useTheme)();
83
+ const tokens = components.menu.item;
84
+ const [isHovered, setIsHovered] = (0, react_1.useState)(false);
85
+ // Danger items use the intent system; neutral items use scheme.menu.item.
86
+ const danger = intent === 'danger';
87
+ const state = disabled
88
+ ? scheme.menu.item.disabled
89
+ : selected && isHovered
90
+ ? scheme.menu.item.selectedHover
91
+ : selected
92
+ ? scheme.menu.item.selected
93
+ : isHovered
94
+ ? scheme.menu.item.hover
95
+ : scheme.menu.item.default;
96
+ const fg = disabled
97
+ ? scheme.menu.item.disabled.fg
98
+ : danger
99
+ ? isHovered
100
+ ? colors.danger.subtle.hover.fg
101
+ : colors.danger.subtle.default.fg
102
+ : state.fg;
103
+ const bg = danger && isHovered && !disabled ? colors.danger.subtle.hover.bg : state.bg;
104
+ const resolvedLeading = typeof leadingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: leadingIcon, size: size, color: fg })) : (leadingIcon);
105
+ const resolvedTrailing = typeof trailingIcon === 'string' ? ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: trailingIcon, size: size, color: fg })) : (trailingIcon);
106
+ const handlePress = (e) => {
107
+ if (disabled)
108
+ return;
109
+ onPress?.(e);
110
+ if (closeOnSelect)
111
+ onClose();
112
+ };
113
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { onPress: handlePress, disabled: disabled, onHoverIn: () => setIsHovered(true), onHoverOut: () => setIsHovered(false), accessibilityRole: "menuitem", accessibilityLabel: accessibilityLabel || children, accessibilityState: { disabled, selected }, style: {
114
+ flexDirection: 'row',
115
+ alignItems: 'center',
116
+ gap: tokens.gap,
117
+ paddingHorizontal: tokens.paddingX,
118
+ paddingVertical: tokens.paddingY,
119
+ borderRadius: tokens.borderRadius,
120
+ backgroundColor: bg,
121
+ }, children: [resolvedLeading, (0, jsx_runtime_1.jsx)(Text_1.Text, { type: LABEL_TYPE[size], color: fg, selectable: false, style: { flex: 1 }, children: children }), shortcut ? ((0, jsx_runtime_1.jsx)(Text_1.Text, { type: "caption", color: scheme.text.description, selectable: false, children: shortcut })) : null, selected && !disabled ? (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "check", size: size, color: fg }) : resolvedTrailing] }));
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // MenuLabel + MenuDivider
125
+ // ---------------------------------------------------------------------------
126
+ function MenuLabel({ children }) {
127
+ const { components, scheme } = (0, theme_1.useTheme)();
128
+ const tokens = components.menu.group;
129
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { paddingHorizontal: tokens.paddingX, paddingVertical: tokens.labelPaddingY }, children: (0, jsx_runtime_1.jsx)(Text_1.Text, { type: "caption", color: scheme.text.description, selectable: false, style: { textTransform: 'uppercase' }, children: children }) }));
130
+ }
131
+ function MenuDivider() {
132
+ const { components, scheme } = (0, theme_1.useTheme)();
133
+ const tokens = components.menu.separator;
134
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: { paddingVertical: tokens.marginY }, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: { height: 1, backgroundColor: scheme.menu.separator } }) }));
135
+ }
136
+ function MenuContent({ children, style }) {
137
+ const { components, scheme } = (0, theme_1.useTheme)();
138
+ const tokens = components.menu.content;
139
+ const surface = scheme.surface;
140
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
141
+ {
142
+ minWidth: MIN_WIDTH,
143
+ backgroundColor: surface.overlay.bg,
144
+ borderWidth: tokens_1.controlTokens.borderWidth,
145
+ borderColor: surface.overlay.border,
146
+ borderRadius: surface.overlay.borderRadius,
147
+ paddingVertical: tokens.paddingY,
148
+ maxHeight: CONTENT_MAX_HEIGHT,
149
+ ...(react_native_1.Platform.OS === 'web' ? SHADOW_WEB : SHADOW_NATIVE),
150
+ },
151
+ style,
152
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { nestedScrollEnabled: true, keyboardShouldPersistTaps: "handled", children: children }) }));
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // Menu — trigger + anchored overlay
156
+ // ---------------------------------------------------------------------------
157
+ /** Absolute-position styles for the four placements. */
158
+ function placementStyle(placement) {
159
+ switch (placement) {
160
+ case 'bottom-end':
161
+ return { position: 'absolute', top: '100%', right: 0, paddingTop: 4 };
162
+ case 'top-start':
163
+ return { position: 'absolute', bottom: '100%', left: 0, paddingBottom: 4 };
164
+ case 'top-end':
165
+ return { position: 'absolute', bottom: '100%', right: 0, paddingBottom: 4 };
166
+ case 'bottom-start':
167
+ default:
168
+ return { position: 'absolute', top: '100%', left: 0, paddingTop: 4 };
169
+ }
170
+ }
171
+ function Menu({ trigger, children, size = 'default', placement = 'bottom-start', open: controlledOpen, onOpenChange, defaultOpen = false, closeOnSelect = true, style, accessibilityLabel, }) {
172
+ const [internalOpen, setInternalOpen] = (0, react_1.useState)(defaultOpen);
173
+ const isControlled = controlledOpen !== undefined;
174
+ const open = isControlled ? controlledOpen : internalOpen;
175
+ const setOpen = (next) => {
176
+ if (!isControlled)
177
+ setInternalOpen(next);
178
+ onOpenChange?.(next);
179
+ };
180
+ // Escape closes the menu on web.
181
+ (0, react_1.useEffect)(() => {
182
+ if (!open || react_native_1.Platform.OS !== 'web')
183
+ return;
184
+ const onKey = (e) => {
185
+ if (e.key === 'Escape') {
186
+ e.stopPropagation();
187
+ setOpen(false);
188
+ }
189
+ };
190
+ document.addEventListener('keydown', onKey);
191
+ return () => document.removeEventListener('keydown', onKey);
192
+ // eslint-disable-next-line react-hooks/exhaustive-deps
193
+ }, [open]);
194
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [{ alignSelf: 'flex-start', position: 'relative', zIndex: open ? 1000 : 0 }, style], children: [(0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => setOpen(!open), accessibilityRole: "button", accessibilityLabel: accessibilityLabel || 'Open menu', accessibilityState: { expanded: open }, children: trigger }), open ? ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { onPress: () => setOpen(false), accessibilityRole: "button", accessibilityLabel: "Close menu", style: react_native_1.Platform.select({
195
+ web: {
196
+ position: 'fixed',
197
+ top: 0,
198
+ left: 0,
199
+ right: 0,
200
+ bottom: 0,
201
+ zIndex: 0,
202
+ },
203
+ default: {
204
+ position: 'absolute',
205
+ top: -9999,
206
+ left: -9999,
207
+ width: 99999,
208
+ height: 99999,
209
+ },
210
+ }) })) : null, open ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [placementStyle(placement), { zIndex: 1 }], children: (0, jsx_runtime_1.jsx)(MenuCtx.Provider, { value: { size, onClose: () => setOpen(false), closeOnSelect }, children: (0, jsx_runtime_1.jsx)(MenuContent, { children: children }) }) })) : null] }));
211
+ }
@@ -0,0 +1 @@
1
+ export { Menu, MenuItem, MenuDivider, MenuLabel, MenuContent, type MenuProps, type MenuItemProps, type MenuLabelProps, type MenuContentProps, type MenuSize, type MenuPlacement, } from './Menu';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MenuContent = exports.MenuLabel = exports.MenuDivider = exports.MenuItem = exports.Menu = void 0;
4
+ var Menu_1 = require("./Menu");
5
+ Object.defineProperty(exports, "Menu", { enumerable: true, get: function () { return Menu_1.Menu; } });
6
+ Object.defineProperty(exports, "MenuItem", { enumerable: true, get: function () { return Menu_1.MenuItem; } });
7
+ Object.defineProperty(exports, "MenuDivider", { enumerable: true, get: function () { return Menu_1.MenuDivider; } });
8
+ Object.defineProperty(exports, "MenuLabel", { enumerable: true, get: function () { return Menu_1.MenuLabel; } });
9
+ Object.defineProperty(exports, "MenuContent", { enumerable: true, get: function () { return Menu_1.MenuContent; } });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Slider — drag a thumb along a track to pick a number.
3
+ *
4
+ * Maps 1:1 to the Figma <Slider> component:
5
+ * intent → neutral | brand | danger (filled portion + thumb ring)
6
+ * size → small | default | large (track thickness + thumb size)
7
+ * value → min..max (single value)
8
+ *
9
+ * Drag the thumb or tap the track to set the value. Built on PanResponder, so
10
+ * it works the same on web and native with zero dependencies. value is
11
+ * controlled with value/onValueChange, or uncontrolled with defaultValue.
12
+ *
13
+ * Tokens: slider/{size}/track-height and slider/{size}/thumb-size are keyed by
14
+ * the size prop and constant across density (like Progress's track-height);
15
+ * slider/border-radius is the pill radius. The track background is the dedicated
16
+ * control/slider/track/bg semantic (scheme.slider.track, cool-grey/200 light,
17
+ * cool-grey/700 dark); the filled portion reuses the intent system
18
+ * (colors[intent].bold.default.bg). No density-varying spacing.
19
+ */
20
+ import { type StyleProp, type ViewStyle } from 'react-native';
21
+ import type { IntentName } from '../../tokens';
22
+ export type SliderSize = 'small' | 'default' | 'large';
23
+ export type SliderProps = {
24
+ /** Current value (controlled). */
25
+ value?: number;
26
+ /** Initial value (uncontrolled). */
27
+ defaultValue?: number;
28
+ /** Called with the new value while dragging or on tap. */
29
+ onValueChange?: (value: number) => void;
30
+ /** Minimum value. Defaults to 0. */
31
+ min?: number;
32
+ /** Maximum value. Defaults to 100. */
33
+ max?: number;
34
+ /** Step increment. Defaults to 1. */
35
+ step?: number;
36
+ /** Semantic intent — drives the fill and thumb-ring colour. */
37
+ intent?: IntentName;
38
+ /** Size variant — track thickness and thumb size. */
39
+ size?: SliderSize;
40
+ /** Disables interaction and applies muted styling. */
41
+ disabled?: boolean;
42
+ /** Outer style — use for positioning (margin, width, alignSelf). */
43
+ style?: StyleProp<ViewStyle>;
44
+ /** Accessibility label — describes what the slider controls. */
45
+ accessibilityLabel?: string;
46
+ };
47
+ export declare function Slider({ value: controlledValue, defaultValue, onValueChange, min, max, step, intent, size, disabled, style, accessibilityLabel, }: SliderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Slider = Slider;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ /**
6
+ * Slider — drag a thumb along a track to pick a number.
7
+ *
8
+ * Maps 1:1 to the Figma <Slider> component:
9
+ * intent → neutral | brand | danger (filled portion + thumb ring)
10
+ * size → small | default | large (track thickness + thumb size)
11
+ * value → min..max (single value)
12
+ *
13
+ * Drag the thumb or tap the track to set the value. Built on PanResponder, so
14
+ * it works the same on web and native with zero dependencies. value is
15
+ * controlled with value/onValueChange, or uncontrolled with defaultValue.
16
+ *
17
+ * Tokens: slider/{size}/track-height and slider/{size}/thumb-size are keyed by
18
+ * the size prop and constant across density (like Progress's track-height);
19
+ * slider/border-radius is the pill radius. The track background is the dedicated
20
+ * control/slider/track/bg semantic (scheme.slider.track, cool-grey/200 light,
21
+ * cool-grey/700 dark); the filled portion reuses the intent system
22
+ * (colors[intent].bold.default.bg). No density-varying spacing.
23
+ */
24
+ const react_1 = require("react");
25
+ const react_native_1 = require("react-native");
26
+ const theme_1 = require("../../theme");
27
+ // ---------------------------------------------------------------------------
28
+ // Constants
29
+ // ---------------------------------------------------------------------------
30
+ /** Thumb ring width. A small visual constant, not a spacing token. */
31
+ const THUMB_RING = 2;
32
+ const SHADOW_WEB = { boxShadow: '0px 1px 3px rgba(0,0,0,0.2)' };
33
+ const SHADOW_NATIVE = {
34
+ shadowColor: '#000000',
35
+ shadowOffset: { width: 0, height: 1 },
36
+ shadowOpacity: 0.2,
37
+ shadowRadius: 2,
38
+ elevation: 2,
39
+ };
40
+ const SHADOW = react_native_1.Platform.OS === 'web' ? SHADOW_WEB : SHADOW_NATIVE;
41
+ const clampFrac = (f) => Math.max(0, Math.min(1, f));
42
+ // ---------------------------------------------------------------------------
43
+ // Component
44
+ // ---------------------------------------------------------------------------
45
+ function Slider({ value: controlledValue, defaultValue = 0, onValueChange, min = 0, max = 100, step = 1, intent = 'brand', size = 'default', disabled = false, style, accessibilityLabel, }) {
46
+ const { components, colors, scheme } = (0, theme_1.useTheme)();
47
+ const { trackHeight, thumbSize } = components.slider[size];
48
+ const borderRadius = components.slider.borderRadius;
49
+ const isControlled = controlledValue !== undefined;
50
+ const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue);
51
+ const value = isControlled ? controlledValue : internalValue;
52
+ const [trackWidth, setTrackWidth] = (0, react_1.useState)(0);
53
+ // Live state for the PanResponder closures (created once).
54
+ const live = (0, react_1.useRef)({
55
+ trackWidth,
56
+ disabled,
57
+ min,
58
+ max,
59
+ step,
60
+ isControlled,
61
+ onValueChange,
62
+ });
63
+ live.current = { trackWidth, disabled, min, max, step, isControlled, onValueChange };
64
+ const startFrac = (0, react_1.useRef)(0);
65
+ const fracToValue = (f) => {
66
+ const { min: lo, max: hi, step: st } = live.current;
67
+ const raw = lo + clampFrac(f) * (hi - lo);
68
+ const snapped = Math.round((raw - lo) / st) * st + lo;
69
+ return Math.max(lo, Math.min(hi, snapped));
70
+ };
71
+ const commit = (v) => {
72
+ if (!live.current.isControlled)
73
+ setInternalValue(v);
74
+ live.current.onValueChange?.(v);
75
+ };
76
+ const pan = (0, react_1.useRef)(react_native_1.PanResponder.create({
77
+ onStartShouldSetPanResponder: () => !live.current.disabled,
78
+ onMoveShouldSetPanResponder: () => !live.current.disabled,
79
+ onPanResponderGrant: (evt) => {
80
+ const w = live.current.trackWidth;
81
+ if (live.current.disabled || w === 0)
82
+ return;
83
+ const f = clampFrac(evt.nativeEvent.locationX / w);
84
+ startFrac.current = f;
85
+ commit(fracToValue(f));
86
+ },
87
+ onPanResponderMove: (_evt, g) => {
88
+ const w = live.current.trackWidth;
89
+ if (live.current.disabled || w === 0)
90
+ return;
91
+ const f = clampFrac(startFrac.current + g.dx / w);
92
+ commit(fracToValue(f));
93
+ },
94
+ })).current;
95
+ const range = max - min;
96
+ const fraction = range > 0 ? clampFrac((value - min) / range) : 0;
97
+ const onLayout = (e) => setTrackWidth(e.nativeEvent.layout.width);
98
+ const trackBg = disabled ? scheme.disabled.bg : scheme.slider.track;
99
+ const fillColor = disabled ? scheme.disabled.fg : colors[intent].bold.default.bg;
100
+ const thumbBorder = disabled ? scheme.disabled.border : colors[intent].bold.default.bg;
101
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { accessibilityRole: "adjustable", accessibilityLabel: accessibilityLabel, accessibilityValue: { min, max, now: Math.round(value) }, accessibilityState: { disabled }, accessibilityActions: [{ name: 'increment' }, { name: 'decrement' }], onAccessibilityAction: (e) => {
102
+ if (disabled)
103
+ return;
104
+ if (e.nativeEvent.actionName === 'increment')
105
+ commit(fracToValue((value - min + step) / (range || 1)));
106
+ if (e.nativeEvent.actionName === 'decrement')
107
+ commit(fracToValue((value - min - step) / (range || 1)));
108
+ }, style: [{ justifyContent: 'center', paddingHorizontal: thumbSize / 2 }, style], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { ...pan.panHandlers, onLayout: onLayout, style: { height: thumbSize, justifyContent: 'center' }, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
109
+ height: trackHeight,
110
+ borderRadius,
111
+ backgroundColor: trackBg,
112
+ overflow: 'hidden',
113
+ }, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: {
114
+ position: 'absolute',
115
+ left: 0,
116
+ top: 0,
117
+ bottom: 0,
118
+ width: trackWidth * fraction,
119
+ backgroundColor: fillColor,
120
+ borderRadius,
121
+ } }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: {
122
+ position: 'absolute',
123
+ left: fraction * trackWidth - thumbSize / 2,
124
+ width: thumbSize,
125
+ height: thumbSize,
126
+ borderRadius: thumbSize / 2,
127
+ backgroundColor: scheme.surface.overlay.bg,
128
+ borderWidth: THUMB_RING,
129
+ borderColor: thumbBorder,
130
+ ...SHADOW,
131
+ } })] }) }));
132
+ }
@@ -0,0 +1 @@
1
+ export { Slider, type SliderProps, type SliderSize } from './Slider';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Slider = void 0;
4
+ var Slider_1 = require("./Slider");
5
+ Object.defineProperty(exports, "Slider", { enumerable: true, get: function () { return Slider_1.Slider; } });