@scalewing/react-native 0.3.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @scalewing/react-native
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Keep public package versions matched for the GitHub `v0.5.0` release tag.
8
+ Native Field ships in this version.
9
+ - 7cf64df: Add an accessible token-styled native `Field` with label, hint,
10
+ error, focus, disabled, and standard React Native text-input behavior.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+ - @scalewing/tokens@0.5.0
16
+
17
+ ## 0.4.0
18
+
19
+ ### Minor Changes
20
+
21
+ - Keep public package versions matched for the GitHub `v0.4.0` release tag.
22
+ Native TabBar, Table, and SegmentedControl ship in this version.
23
+ - feef1f8: Add native SegmentedControl with radiogroup semantics, a glass pill track, and a surface-filled selected item. Labels stay in the consumer.
24
+ - feef1f8: Add native TabBar with selected tab semantics, icon slots, and an optional trailing control for round search-style actions.
25
+ - feef1f8: Add native Table with compact density, numeric cells, and the same row/cell language as the web table.
26
+
27
+ ### Patch Changes
28
+
29
+ - 01b138f: Point package metadata to the GitHub source repository and document explicit GitHub Actions releases. Package names and runtime APIs are unchanged.
30
+ - feef1f8: Allow optional `onPress` on native TableRow so compact score rows can open a detail screen without a product-only MatchRow.
31
+ - Updated dependencies [01b138f]
32
+ - Updated dependencies
33
+ - @scalewing/tokens@0.4.0
34
+
3
35
  ## 0.3.0
4
36
 
5
37
  ### Minor Changes
package/README.md CHANGED
@@ -3,18 +3,24 @@
3
3
  React Native / Expo primitives that consume the same Scalewing tokens as the DOM package.
4
4
 
5
5
  ```ts
6
- import { Button, Card, Stack, Text, ThemeProvider } from '@scalewing/react-native';
6
+ import { Button, Card, Field, SegmentedControl, Stack, TabBar, Text, ThemeProvider } from '@scalewing/react-native';
7
7
 
8
8
  <ThemeProvider colorScheme="system" palette="cerulean">
9
9
  <Card padding={4}>
10
10
  <Stack gap={3}>
11
11
  <Text variant="title">Kickoff</Text>
12
+ <Field label="Team name" onChangeText={() => undefined} value="Harbor United" />
12
13
  <Button onPress={() => undefined}>Save</Button>
13
14
  </Stack>
14
15
  </Card>
15
16
  </ThemeProvider>
16
17
  ```
17
18
 
18
- There is no CSS class API. `padding={4}` is spacing step 4, the same step as `sw-padding-4` on web. Field is web-only (`@scalewing/react`). `colorScheme` is `"light"`, `"dark"`, or `"system"`. Named palettes include both schemes; `colors` may be `{ light, dark }`.
19
+ There is no CSS class API. `padding={4}` is spacing step 4, the same step as
20
+ `sw-padding-4` on web. Native `Field` renders a controlled `TextInput` with a
21
+ visible label and optional hint or error; it accepts standard text-input props
22
+ except styling and controlled semantics owned by the component. `colorScheme`
23
+ is `"light"`, `"dark"`, or `"system"`. Named palettes include both schemes;
24
+ `colors` may be `{ light, dark }`.
19
25
 
20
26
  React and React Native are peer dependencies so the host Expo app owns the runtime. Rationale: duplicating React Native inside this package would fight Metro and Expo upgrades.
@@ -1,5 +1,5 @@
1
1
  import { type FlexAlignType, type FlexStyle } from 'react-native';
2
2
  export type Align = 'start' | 'center' | 'end' | 'stretch';
3
- export type Justify = 'start' | 'center' | 'end' | 'between';
3
+ export type Justify = 'start' | 'center' | 'end' | 'between' | 'around';
4
4
  export declare const alignItems: Record<Align, FlexAlignType>;
5
5
  export declare const justifyContent: Record<Justify, FlexStyle['justifyContent']>;
package/dist/alignment.js CHANGED
@@ -5,6 +5,7 @@ export const alignItems = {
5
5
  stretch: 'stretch',
6
6
  };
7
7
  export const justifyContent = {
8
+ around: 'space-around',
8
9
  between: 'space-between',
9
10
  center: 'center',
10
11
  end: 'flex-end',
@@ -0,0 +1,10 @@
1
+ import { type TextInputProps } from 'react-native';
2
+ export type FieldProps = Omit<TextInputProps, 'accessibilityLabel' | 'accessibilityState' | 'editable' | 'onChangeText' | 'style' | 'value'> & {
3
+ disabled?: boolean;
4
+ error?: string;
5
+ hint?: string;
6
+ label: string;
7
+ onChangeText: (value: string) => void;
8
+ value: string;
9
+ };
10
+ export declare function Field({ disabled, error, hint, label, onBlur, onChangeText, onFocus, value, ...inputProps }: FieldProps): import("react").JSX.Element;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { TextInput } from 'react-native';
4
+ import { mapFieldAccessibility } from '../map-field-accessibility.js';
5
+ import { mapFieldInputStyle } from '../map-field-style.js';
6
+ import { useTheme } from '../theme/ThemeProvider.js';
7
+ import { Stack } from './Stack.js';
8
+ import { Text } from './Text.js';
9
+ export function Field({ disabled = false, error, hint, label, onBlur, onChangeText, onFocus, value, ...inputProps }) {
10
+ const theme = useTheme();
11
+ const [focused, setFocused] = useState(false);
12
+ const supportingText = error ?? hint;
13
+ return (_jsxs(Stack, { gap: 1, children: [_jsx(Text, { variant: "label", children: label }), _jsx(TextInput, { ...inputProps, ...mapFieldAccessibility({
14
+ accessibilityHint: inputProps.accessibilityHint,
15
+ disabled,
16
+ error,
17
+ hint,
18
+ label,
19
+ }), editable: !disabled, onBlur: (event) => {
20
+ setFocused(false);
21
+ onBlur?.(event);
22
+ }, onChangeText: onChangeText, onFocus: (event) => {
23
+ setFocused(true);
24
+ onFocus?.(event);
25
+ }, placeholderTextColor: theme.colors.muted, style: mapFieldInputStyle(theme, {
26
+ disabled,
27
+ focused,
28
+ invalid: Boolean(error),
29
+ }), value: value }), supportingText ? (_jsx(Text, { accessibilityLiveRegion: error ? 'polite' : 'none', color: error ? 'danger' : 'muted', variant: "caption", children: supportingText })) : null] }));
30
+ }
@@ -0,0 +1,11 @@
1
+ export type SegmentedItem = {
2
+ id: string;
3
+ label: string;
4
+ };
5
+ export type SegmentedControlProps = {
6
+ accessibilityLabel: string;
7
+ items: readonly SegmentedItem[];
8
+ onChange: (id: string) => void;
9
+ value: string;
10
+ };
11
+ export declare function SegmentedControl({ accessibilityLabel, items, onChange, value, }: SegmentedControlProps): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Pressable } from 'react-native';
3
+ import { mapSegmentedControlStyle, mapSegmentedItemStyle, segmentedItemColor, } from '../map-segmented-style.js';
4
+ import { useTheme } from '../theme/ThemeProvider.js';
5
+ import { Box } from './Box.js';
6
+ import { Text } from './Text.js';
7
+ export function SegmentedControl({ accessibilityLabel, items, onChange, value, }) {
8
+ const theme = useTheme();
9
+ return (_jsx(Box, { accessibilityLabel: accessibilityLabel, accessibilityRole: "radiogroup", style: mapSegmentedControlStyle(theme), children: items.map((item) => {
10
+ const selected = item.id === value;
11
+ return (_jsx(Pressable, { accessibilityRole: "radio", accessibilityState: { selected }, onPress: () => {
12
+ if (!selected) {
13
+ onChange(item.id);
14
+ }
15
+ }, style: mapSegmentedItemStyle(theme, selected), children: _jsx(Text, { color: segmentedItemColor(selected), variant: "label", children: item.label }) }, item.id));
16
+ }) }));
17
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ export type TabBarItem = {
3
+ icon: ReactNode;
4
+ key: string;
5
+ label: string;
6
+ onPress: () => void;
7
+ selected: boolean;
8
+ };
9
+ export type TabBarProps = {
10
+ bottomInset?: number;
11
+ items: readonly TabBarItem[];
12
+ trailing?: ReactNode;
13
+ };
14
+ export type TabBarTrailingProps = {
15
+ accessibilityLabel: string;
16
+ children: ReactNode;
17
+ onPress: () => void;
18
+ };
19
+ export declare function TabBarTrailing({ accessibilityLabel, children, onPress, }: TabBarTrailingProps): import("react").JSX.Element;
20
+ export declare function TabBar({ bottomInset, items, trailing }: TabBarProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pressable } from 'react-native';
3
+ import { mapTabBarStyle, mapTabBarTrailingStyle, tabBarItemColor, } from '../map-tab-bar-style.js';
4
+ import { useTheme } from '../theme/ThemeProvider.js';
5
+ import { Box } from './Box.js';
6
+ import { Inline } from './Inline.js';
7
+ import { Stack } from './Stack.js';
8
+ import { Text } from './Text.js';
9
+ export function TabBarTrailing({ accessibilityLabel, children, onPress, }) {
10
+ const theme = useTheme();
11
+ return (_jsx(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", onPress: onPress, style: mapTabBarTrailingStyle(theme), children: children }));
12
+ }
13
+ export function TabBar({ bottomInset = 0, items, trailing }) {
14
+ const theme = useTheme();
15
+ return (_jsx(Box, { accessibilityRole: "tablist", style: mapTabBarStyle(theme, { bottomInset }), children: _jsxs(Inline, { align: "center", gap: 3, children: [_jsx(Inline, { align: "center", justify: "around", style: { flex: 1 }, children: items.map((item) => (_jsx(Pressable, { accessibilityLabel: item.label, accessibilityRole: "tab", accessibilityState: { selected: item.selected }, onPress: item.onPress, children: _jsxs(Stack, { align: "center", gap: 1, children: [item.icon, _jsx(Text, { color: tabBarItemColor(item.selected), variant: "caption", children: item.label })] }) }, item.key))) }), trailing] }) }));
16
+ }
@@ -0,0 +1,31 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type TableDensity } from '../map-table-style.js';
3
+ export type TableProps = {
4
+ children: ReactNode;
5
+ density?: TableDensity;
6
+ };
7
+ export declare function Table({ children, density }: TableProps): import("react").JSX.Element;
8
+ export type TableHeaderProps = {
9
+ children: ReactNode;
10
+ };
11
+ export declare function TableHeader({ children }: TableHeaderProps): import("react").JSX.Element;
12
+ export type TableBodyProps = {
13
+ children: ReactNode;
14
+ };
15
+ export declare function TableBody({ children }: TableBodyProps): import("react").JSX.Element;
16
+ export type TableRowProps = {
17
+ accessibilityLabel?: string;
18
+ children: ReactNode;
19
+ onPress?: () => void;
20
+ };
21
+ export declare function TableRow({ accessibilityLabel, children, onPress, }: TableRowProps): import("react").JSX.Element;
22
+ export type TableCellProps = {
23
+ align?: 'start' | 'center' | 'end';
24
+ children: ReactNode;
25
+ flex?: number;
26
+ header?: boolean;
27
+ numeric?: boolean;
28
+ truncate?: boolean;
29
+ };
30
+ export declare function TableCell({ align, children, flex, header, numeric, truncate, }: TableCellProps): import("react").JSX.Element;
31
+ export type { TableDensity };
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from 'react';
3
+ import { Pressable } from 'react-native';
4
+ import { Box } from './Box.js';
5
+ import { Text } from './Text.js';
6
+ import { useTheme } from '../theme/ThemeProvider.js';
7
+ import { mapTableCellStyle, mapTableRowStyle, } from '../map-table-style.js';
8
+ const TableContext = createContext({
9
+ density: 'compact',
10
+ });
11
+ export function Table({ children, density = 'compact' }) {
12
+ return (_jsx(TableContext.Provider, { value: { density }, children: _jsx(Box, { children: children }) }));
13
+ }
14
+ export function TableHeader({ children }) {
15
+ return _jsx(Box, { children: children });
16
+ }
17
+ export function TableBody({ children }) {
18
+ return _jsx(Box, { children: children });
19
+ }
20
+ export function TableRow({ accessibilityLabel, children, onPress, }) {
21
+ const theme = useTheme();
22
+ const { density } = useContext(TableContext);
23
+ if (onPress) {
24
+ return (_jsx(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", onPress: onPress, style: ({ pressed }) => mapTableRowStyle(theme, { density, pressed }), children: children }));
25
+ }
26
+ return _jsx(Box, { style: mapTableRowStyle(theme, { density }), children: children });
27
+ }
28
+ export function TableCell({ align = 'start', children, flex = 1, header = false, numeric = false, truncate = false, }) {
29
+ return (_jsx(Box, { style: mapTableCellStyle({ align, flex, numeric }), children: typeof children === 'string' || typeof children === 'number' ? (_jsx(Text, { color: header ? 'muted' : 'text', truncate: truncate, variant: numeric ? 'data' : 'caption', children: children })) : (children) }));
30
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export { Box, type BoxProps } from './components/Box.js';
2
2
  export { Button, type ButtonProps, type ButtonSize, type ButtonVariant, } from './components/Button.js';
3
3
  export { Card, type CardProps, type CardVariant } from './components/Card.js';
4
+ export { Field, type FieldProps } from './components/Field.js';
4
5
  export { Inline, type InlineProps } from './components/Inline.js';
5
6
  export { Stack, type StackProps } from './components/Stack.js';
6
7
  export { type Align, type Justify } from './alignment.js';
8
+ export { SegmentedControl, type SegmentedControlProps, type SegmentedItem, } from './components/SegmentedControl.js';
9
+ export { TabBar, TabBarTrailing, type TabBarItem, type TabBarProps, type TabBarTrailingProps, } from './components/TabBar.js';
10
+ export { Table, TableBody, TableCell, TableHeader, TableRow, type TableBodyProps, type TableCellProps, type TableDensity, type TableHeaderProps, type TableProps, type TableRowProps, } from './components/Table.js';
7
11
  export { Text, type TextProps } from './components/Text.js';
8
12
  export { ThemeProvider, useTheme, type ThemeProviderProps, } from './theme/ThemeProvider.js';
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  export { Box } from './components/Box.js';
2
2
  export { Button, } from './components/Button.js';
3
3
  export { Card } from './components/Card.js';
4
+ export { Field } from './components/Field.js';
4
5
  export { Inline } from './components/Inline.js';
5
6
  export { Stack } from './components/Stack.js';
7
+ export { SegmentedControl, } from './components/SegmentedControl.js';
8
+ export { TabBar, TabBarTrailing, } from './components/TabBar.js';
9
+ export { Table, TableBody, TableCell, TableHeader, TableRow, } from './components/Table.js';
6
10
  export { Text } from './components/Text.js';
7
11
  export { ThemeProvider, useTheme, } from './theme/ThemeProvider.js';
@@ -0,0 +1,10 @@
1
+ import { type TextInputProps } from 'react-native';
2
+ type FieldAccessibilityProps = Pick<TextInputProps, 'accessibilityHint' | 'accessibilityLabel' | 'accessibilityState'>;
3
+ export declare function mapFieldAccessibility(options: {
4
+ accessibilityHint?: string;
5
+ disabled: boolean;
6
+ error?: string;
7
+ hint?: string;
8
+ label: string;
9
+ }): FieldAccessibilityProps;
10
+ export {};
@@ -0,0 +1,7 @@
1
+ export function mapFieldAccessibility(options) {
2
+ return {
3
+ accessibilityHint: options.error ?? options.hint ?? options.accessibilityHint,
4
+ accessibilityLabel: options.label,
5
+ accessibilityState: { disabled: options.disabled },
6
+ };
7
+ }
@@ -0,0 +1,7 @@
1
+ import { type Theme } from '@scalewing/tokens';
2
+ import { type TextStyle } from 'react-native';
3
+ export declare function mapFieldInputStyle(theme: Theme, options: {
4
+ disabled: boolean;
5
+ focused: boolean;
6
+ invalid: boolean;
7
+ }): TextStyle;
@@ -0,0 +1,22 @@
1
+ export function mapFieldInputStyle(theme, options) {
2
+ const type = theme.typography.body;
3
+ return {
4
+ backgroundColor: theme.colors.surface,
5
+ borderColor: options.invalid
6
+ ? theme.colors.danger
7
+ : options.focused
8
+ ? theme.colors.accent
9
+ : theme.colors.border,
10
+ borderRadius: theme.radius.lg,
11
+ borderWidth: 1,
12
+ color: theme.colors.text,
13
+ fontSize: type.fontSize,
14
+ fontWeight: String(type.fontWeight),
15
+ letterSpacing: type.letterSpacing,
16
+ lineHeight: type.lineHeight,
17
+ minHeight: theme.control.md.minHeight,
18
+ opacity: options.disabled ? theme.disabledOpacity : 1,
19
+ paddingHorizontal: theme.control.md.paddingInline,
20
+ paddingVertical: theme.space[2],
21
+ };
22
+ }
@@ -0,0 +1,5 @@
1
+ import { type SemanticColorKey, type Theme } from '@scalewing/tokens';
2
+ import { type ViewStyle } from 'react-native';
3
+ export declare function mapSegmentedControlStyle(theme: Theme): ViewStyle;
4
+ export declare function mapSegmentedItemStyle(theme: Theme, selected: boolean): ViewStyle;
5
+ export declare function segmentedItemColor(selected: boolean): SemanticColorKey;
@@ -0,0 +1,26 @@
1
+ import { trackInset, } from '@scalewing/tokens';
2
+ export function mapSegmentedControlStyle(theme) {
3
+ return {
4
+ backgroundColor: theme.glass.fill,
5
+ borderColor: theme.glass.border,
6
+ borderRadius: theme.radius.pill,
7
+ borderWidth: 1,
8
+ flexDirection: 'row',
9
+ gap: trackInset,
10
+ padding: trackInset,
11
+ };
12
+ }
13
+ export function mapSegmentedItemStyle(theme, selected) {
14
+ return {
15
+ alignItems: 'center',
16
+ backgroundColor: selected ? theme.colors.surface : 'transparent',
17
+ borderRadius: theme.radius.pill,
18
+ flex: 1,
19
+ justifyContent: 'center',
20
+ minHeight: theme.control.xs.minHeight,
21
+ paddingHorizontal: theme.control.xs.paddingInline,
22
+ };
23
+ }
24
+ export function segmentedItemColor(selected) {
25
+ return selected ? 'text' : 'muted';
26
+ }
@@ -0,0 +1,7 @@
1
+ import { type SemanticColorKey, type Theme } from '@scalewing/tokens';
2
+ import { type ViewStyle } from 'react-native';
3
+ export declare function mapTabBarStyle(theme: Theme, options?: {
4
+ bottomInset: number;
5
+ }): ViewStyle;
6
+ export declare function tabBarItemColor(selected: boolean): SemanticColorKey;
7
+ export declare function mapTabBarTrailingStyle(theme: Theme): ViewStyle;
@@ -0,0 +1,26 @@
1
+ export function mapTabBarStyle(theme, options = { bottomInset: 0 }) {
2
+ return {
3
+ backgroundColor: theme.colors.surface,
4
+ borderTopColor: theme.colors.border,
5
+ borderTopWidth: 1,
6
+ paddingBottom: theme.space[2] + options.bottomInset,
7
+ paddingHorizontal: theme.space[3],
8
+ paddingTop: theme.space[2],
9
+ };
10
+ }
11
+ export function tabBarItemColor(selected) {
12
+ return selected ? 'accent' : 'muted';
13
+ }
14
+ export function mapTabBarTrailingStyle(theme) {
15
+ const size = theme.control.md.minHeight;
16
+ return {
17
+ alignItems: 'center',
18
+ backgroundColor: theme.colors.surface,
19
+ borderColor: theme.colors.border,
20
+ borderRadius: theme.radius.pill,
21
+ borderWidth: 1,
22
+ height: size,
23
+ justifyContent: 'center',
24
+ width: size,
25
+ };
26
+ }
@@ -0,0 +1,12 @@
1
+ import { type Theme } from '@scalewing/tokens';
2
+ import { type ViewStyle } from 'react-native';
3
+ export type TableDensity = 'comfortable' | 'compact';
4
+ export declare function mapTableRowStyle(theme: Theme, options: {
5
+ density: TableDensity;
6
+ pressed?: boolean;
7
+ }): ViewStyle;
8
+ export declare function mapTableCellStyle(options: {
9
+ align: 'start' | 'center' | 'end';
10
+ flex: number;
11
+ numeric: boolean;
12
+ }): ViewStyle;
@@ -0,0 +1,24 @@
1
+ export function mapTableRowStyle(theme, options) {
2
+ const compact = options.density === 'compact';
3
+ return {
4
+ alignItems: 'center',
5
+ borderBottomColor: theme.colors.border,
6
+ borderBottomWidth: 1,
7
+ flexDirection: 'row',
8
+ opacity: options.pressed ? theme.disabledOpacity : 1,
9
+ paddingHorizontal: compact ? theme.space[2] : theme.space[3],
10
+ paddingVertical: compact ? theme.space[1] : theme.space[2],
11
+ };
12
+ }
13
+ export function mapTableCellStyle(options) {
14
+ const alignment = options.align === 'center'
15
+ ? 'center'
16
+ : options.numeric || options.align === 'end'
17
+ ? 'flex-end'
18
+ : 'flex-start';
19
+ return {
20
+ alignItems: alignment,
21
+ flex: options.flex,
22
+ minWidth: 0,
23
+ };
24
+ }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@scalewing/react-native",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Scalewing React Native layout primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://gitlab.com/dna-consulting/scalewing.git",
9
+ "url": "git+https://github.com/nestor-garcia-dev/scalewing.git",
10
10
  "directory": "packages/react-native"
11
11
  },
12
- "homepage": "https://gitlab.com/dna-consulting/scalewing#readme",
12
+ "homepage": "https://github.com/nestor-garcia-dev/scalewing#readme",
13
13
  "bugs": {
14
- "url": "https://gitlab.com/dna-consulting/scalewing/-/issues"
14
+ "url": "https://github.com/nestor-garcia-dev/scalewing/issues"
15
15
  },
16
16
  "sideEffects": false,
17
17
  "exports": {
@@ -28,7 +28,7 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@scalewing/tokens": "0.3.0"
31
+ "@scalewing/tokens": "0.5.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": "^19.0.0",