@idealyst/components 1.2.98 → 1.2.100

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.98",
3
+ "version": "1.2.100",
4
4
  "description": "Shared component library for React and React Native",
5
5
  "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
6
6
  "readme": "README.md",
@@ -56,7 +56,7 @@
56
56
  "publish:npm": "npm publish"
57
57
  },
58
58
  "peerDependencies": {
59
- "@idealyst/theme": "^1.2.98",
59
+ "@idealyst/theme": "^1.2.100",
60
60
  "@mdi/js": ">=7.0.0",
61
61
  "@mdi/react": ">=1.0.0",
62
62
  "@react-native-vector-icons/common": ">=12.0.0",
@@ -107,8 +107,8 @@
107
107
  },
108
108
  "devDependencies": {
109
109
  "@idealyst/blur": "^1.2.40",
110
- "@idealyst/theme": "^1.2.98",
111
- "@idealyst/tooling": "^1.2.98",
110
+ "@idealyst/theme": "^1.2.100",
111
+ "@idealyst/tooling": "^1.2.100",
112
112
  "@mdi/react": "^1.6.1",
113
113
  "@types/react": "^19.1.0",
114
114
  "react": "^19.1.0",
@@ -0,0 +1,102 @@
1
+ import React, { forwardRef, useMemo, Children } from 'react';
2
+ import { View } from 'react-native';
3
+ import { useResponsiveStyle, isResponsiveValue, Responsive } from '@idealyst/theme';
4
+ import { UnistylesRuntime } from 'react-native-unistyles';
5
+ import { GridProps } from './types';
6
+ import { gridStyles } from './Grid.styles';
7
+ import type { IdealystElement } from '../utils/refTypes';
8
+
9
+ /**
10
+ * Resolve a responsive number value to a concrete number for the current breakpoint.
11
+ */
12
+ function useResolvedColumns(columns: Responsive<number>): number {
13
+ const resolved = useResponsiveStyle(
14
+ isResponsiveValue(columns)
15
+ ? { zIndex: columns as any }
16
+ : {}
17
+ );
18
+
19
+ if (isResponsiveValue(columns)) {
20
+ return (resolved.zIndex as number) ?? 1;
21
+ }
22
+
23
+ return columns;
24
+ }
25
+
26
+ /**
27
+ * Get the gap value in pixels from the theme for a given size key.
28
+ */
29
+ function getGapPixels(size: string): number {
30
+ const theme = UnistylesRuntime.getTheme();
31
+ const sizes = (theme as any).sizes?.view;
32
+ return sizes?.[size]?.spacing ?? 16;
33
+ }
34
+
35
+ /**
36
+ * Grid component for React Native.
37
+ *
38
+ * Uses flexbox wrapping with calculated percentage widths to simulate
39
+ * a grid layout. Each child is wrapped in a cell view that handles sizing.
40
+ */
41
+ const Grid = forwardRef<IdealystElement, GridProps>(({
42
+ children,
43
+ columns = 1,
44
+ gap = 'md',
45
+ padding,
46
+ paddingVertical,
47
+ paddingHorizontal,
48
+ margin,
49
+ marginVertical,
50
+ marginHorizontal,
51
+ style,
52
+ testID,
53
+ id,
54
+ onLayout,
55
+ }, ref) => {
56
+ const resolvedColumns = useResolvedColumns(columns);
57
+ const gapPx = getGapPixels(gap);
58
+
59
+ gridStyles.useVariants({
60
+ gap,
61
+ padding,
62
+ paddingVertical,
63
+ paddingHorizontal,
64
+ margin,
65
+ marginVertical,
66
+ marginHorizontal,
67
+ });
68
+
69
+ // Calculate cell width accounting for gaps between columns.
70
+ // Total gap space = gapPx * (columns - 1), divided equally among all columns.
71
+ const cellStyle = useMemo(() => {
72
+ const totalGapSpace = gapPx * (resolvedColumns - 1);
73
+ return {
74
+ flexBasis: `${((1 / resolvedColumns) * 100).toFixed(4)}%` as any,
75
+ maxWidth: `${((1 / resolvedColumns) * 100).toFixed(4)}%` as any,
76
+ // Subtract proportional gap from each cell using negative margin + padding trick
77
+ // is fragile, so we use the gap property on the container which RN supports.
78
+ };
79
+ }, [resolvedColumns, gapPx]);
80
+
81
+ const items = Children.toArray(children);
82
+
83
+ return (
84
+ <View
85
+ ref={ref as any}
86
+ style={[gridStyles.grid as any, style]}
87
+ testID={testID}
88
+ nativeID={id}
89
+ onLayout={onLayout}
90
+ >
91
+ {items.map((child, index) => (
92
+ <View key={(child as any).key ?? index} style={cellStyle}>
93
+ {child}
94
+ </View>
95
+ ))}
96
+ </View>
97
+ );
98
+ });
99
+
100
+ Grid.displayName = 'Grid';
101
+
102
+ export default Grid;
@@ -0,0 +1,56 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
3
+ import type { Theme as BaseTheme } from '@idealyst/theme';
4
+ import { ViewStyleSize } from '../utils/viewStyleProps';
5
+
6
+ // Required: Unistyles must see StyleSheet usage in original source
7
+ void StyleSheet;
8
+
9
+ type Theme = ThemeStyleWrapper<BaseTheme>;
10
+
11
+ export type GridVariants = {
12
+ gap: ViewStyleSize;
13
+ padding: ViewStyleSize;
14
+ paddingVertical: ViewStyleSize;
15
+ paddingHorizontal: ViewStyleSize;
16
+ margin: ViewStyleSize;
17
+ marginVertical: ViewStyleSize;
18
+ marginHorizontal: ViewStyleSize;
19
+ };
20
+
21
+ export const gridStyles = defineStyle('Grid', (theme: Theme) => ({
22
+ grid: {
23
+ // Theme marker for Unistyles reactivity
24
+ borderColor: theme.colors.border.primary,
25
+ borderWidth: 0,
26
+ flexDirection: 'row' as const,
27
+ flexWrap: 'wrap' as const,
28
+ variants: {
29
+ gap: {
30
+ gap: theme.sizes.$view.spacing,
31
+ },
32
+ padding: {
33
+ padding: theme.sizes.$view.padding,
34
+ },
35
+ paddingVertical: {
36
+ paddingVertical: theme.sizes.$view.padding,
37
+ },
38
+ paddingHorizontal: {
39
+ paddingHorizontal: theme.sizes.$view.padding,
40
+ },
41
+ margin: {
42
+ margin: theme.sizes.$view.padding,
43
+ },
44
+ marginVertical: {
45
+ marginVertical: theme.sizes.$view.padding,
46
+ },
47
+ marginHorizontal: {
48
+ marginHorizontal: theme.sizes.$view.padding,
49
+ },
50
+ },
51
+ _web: {
52
+ display: 'grid',
53
+ boxSizing: 'border-box',
54
+ },
55
+ },
56
+ }));
@@ -0,0 +1,88 @@
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { useBreakpoint, Breakpoint } from '@idealyst/theme';
4
+ import { GridProps, ResponsiveColumns } from './types';
5
+ import { gridStyles } from './Grid.styles';
6
+ import useMergeRefs from '../hooks/useMergeRefs';
7
+ import { useWebLayout } from '../hooks/useWebLayout';
8
+ import type { IdealystElement } from '../utils/refTypes';
9
+ import { flattenStyle } from '../utils/flattenStyle';
10
+
11
+ const BREAKPOINT_ORDER: Breakpoint[] = ['xl', 'lg', 'md', 'sm', 'xs'];
12
+
13
+ function resolveColumns(columns: ResponsiveColumns, breakpoint: Breakpoint | undefined): number {
14
+ if (typeof columns === 'number') return columns;
15
+
16
+ const bpIndex = breakpoint ? BREAKPOINT_ORDER.indexOf(breakpoint) : BREAKPOINT_ORDER.length - 1;
17
+ for (let i = bpIndex; i < BREAKPOINT_ORDER.length; i++) {
18
+ const bp = BREAKPOINT_ORDER[i];
19
+ if (columns[bp] !== undefined) {
20
+ return columns[bp]!;
21
+ }
22
+ }
23
+
24
+ return 1;
25
+ }
26
+
27
+ /**
28
+ * Grid component for Web.
29
+ *
30
+ * Uses CSS Grid for efficient, native grid layout with responsive column counts.
31
+ */
32
+ const Grid = forwardRef<IdealystElement, GridProps>(({
33
+ children,
34
+ columns = 1,
35
+ gap: gapProp = 'md',
36
+ padding,
37
+ paddingVertical,
38
+ paddingHorizontal,
39
+ margin,
40
+ marginVertical,
41
+ marginHorizontal,
42
+ style,
43
+ testID,
44
+ id,
45
+ onLayout,
46
+ }, ref) => {
47
+ const layoutRef = useWebLayout<HTMLDivElement>(onLayout);
48
+ const breakpoint = useBreakpoint();
49
+
50
+ const resolvedColumns = useMemo(
51
+ () => resolveColumns(columns, breakpoint),
52
+ [columns, breakpoint]
53
+ );
54
+
55
+ gridStyles.useVariants({
56
+ gap: gapProp,
57
+ padding,
58
+ paddingVertical,
59
+ paddingHorizontal,
60
+ margin,
61
+ marginVertical,
62
+ marginHorizontal,
63
+ });
64
+
65
+ const webProps = getWebProps([gridStyles.grid, flattenStyle(style)]);
66
+ const mergedRef = useMergeRefs(ref, webProps.ref, layoutRef);
67
+
68
+ const gridTemplateColumns = `repeat(${resolvedColumns}, 1fr)`;
69
+
70
+ return (
71
+ <div
72
+ {...webProps}
73
+ ref={mergedRef as any}
74
+ id={id}
75
+ data-testid={testID}
76
+ style={{
77
+ ...webProps.style,
78
+ gridTemplateColumns,
79
+ }}
80
+ >
81
+ {children}
82
+ </div>
83
+ );
84
+ });
85
+
86
+ Grid.displayName = 'Grid';
87
+
88
+ export default Grid;
@@ -0,0 +1,2 @@
1
+ export { default } from './Grid.native';
2
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import GridComponent from './Grid.web';
2
+
3
+ export default GridComponent;
4
+ export { GridComponent as Grid };
5
+ export * from './types';
@@ -0,0 +1,5 @@
1
+ import GridComponent from './Grid.web';
2
+
3
+ export default GridComponent;
4
+ export { GridComponent as Grid };
5
+ export * from './types';
@@ -0,0 +1,73 @@
1
+ import { Size } from '@idealyst/theme';
2
+ import type { Breakpoint } from '@idealyst/theme';
3
+ import type { ReactNode } from 'react';
4
+ import type { StyleProp, ViewStyle } from 'react-native';
5
+ import { ContainerStyleProps } from '../utils/viewStyleProps';
6
+ import type { LayoutChangeEvent } from '../hooks/useWebLayout';
7
+
8
+ export type { LayoutChangeEvent };
9
+
10
+ /**
11
+ * Responsive column count - either a fixed number or a breakpoint map.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * // Fixed columns
16
+ * <Grid columns={3} />
17
+ *
18
+ * // Responsive columns
19
+ * <Grid columns={{ xs: 1, sm: 2, lg: 4 }} />
20
+ * ```
21
+ */
22
+ export type ResponsiveColumns = number | Partial<Record<Breakpoint, number>>;
23
+
24
+ /**
25
+ * Gap size between grid items. Uses theme spacing values.
26
+ */
27
+ export type GridGap = Size;
28
+
29
+ /**
30
+ * Cross-platform grid layout component.
31
+ *
32
+ * Arranges children into columns with consistent gap spacing.
33
+ * Supports responsive column counts that adapt to screen size.
34
+ *
35
+ * - **Web**: Uses CSS Grid for optimal layout performance.
36
+ * - **Native**: Uses percentage-based widths with flexbox wrapping.
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <Grid columns={{ xs: 1, sm: 2, lg: 4 }} gap="md">
41
+ * <Card>Item 1</Card>
42
+ * <Card>Item 2</Card>
43
+ * <Card>Item 3</Card>
44
+ * <Card>Item 4</Card>
45
+ * </Grid>
46
+ * ```
47
+ */
48
+ export interface GridProps extends ContainerStyleProps {
49
+ /** Grid content - each direct child becomes a grid item */
50
+ children?: ReactNode;
51
+
52
+ /**
53
+ * Number of columns. Can be a fixed number or responsive breakpoint map.
54
+ * @default 1
55
+ */
56
+ columns?: ResponsiveColumns;
57
+
58
+ /**
59
+ * Gap between grid items (both rows and columns).
60
+ * Uses theme spacing values (theme.sizes.view[size].spacing).
61
+ * @default 'md'
62
+ */
63
+ gap?: GridGap;
64
+
65
+ /** Additional styles applied to the grid container */
66
+ style?: StyleProp<ViewStyle>;
67
+
68
+ /** Test ID for testing */
69
+ testID?: string;
70
+
71
+ /** Called when the grid's layout changes */
72
+ onLayout?: (event: LayoutChangeEvent) => void;
73
+ }
@@ -41,10 +41,8 @@ const Progress = forwardRef<IdealystElement, ProgressProps>(({
41
41
  rounded,
42
42
  });
43
43
 
44
- // Compute dynamic styles with intent
45
- const dynamicProps = { intent };
46
- const linearBarStyle = (progressStyles.linearBar as any)(dynamicProps);
47
- const indeterminateBarStyle = (progressStyles.indeterminateBar as any)(dynamicProps);
44
+ const linearBarStyle = (progressStyles.linearBar as any);
45
+ const indeterminateBarStyle = (progressStyles.indeterminateBar as any);
48
46
 
49
47
  // Animation values
50
48
  const animatedValue = useSharedValue(0);
@@ -163,8 +161,8 @@ const Progress = forwardRef<IdealystElement, ProgressProps>(({
163
161
  });
164
162
 
165
163
  // Get dynamic styles for extended styles
166
- const containerStyle = (progressStyles.container as any)({});
167
- const linearTrackStyle = (progressStyles.linearTrack as any)({});
164
+ const containerStyle = (progressStyles.container as any);
165
+ const linearTrackStyle = (progressStyles.linearTrack as any);
168
166
 
169
167
  return (
170
168
  <View ref={ref as any} nativeID={id} style={[containerStyle, style]} testID={testID} accessibilityRole="progressbar">
@@ -0,0 +1,108 @@
1
+ import { Screen, View, Text, Card, Grid } from '../index';
2
+
3
+ export const GridExamples = () => {
4
+ return (
5
+ <Screen background="primary" padding="lg">
6
+ <View gap="xl">
7
+ <Text typography="h4" align="center">
8
+ Grid Examples
9
+ </Text>
10
+
11
+ {/* Fixed Columns */}
12
+ <View gap="md">
13
+ <Text typography="subtitle1">Fixed Columns</Text>
14
+ <Grid columns={3} gap="md">
15
+ {Array.from({ length: 6 }, (_, i) => (
16
+ <Card key={i} type="outlined" padding="md">
17
+ <Text weight="medium">Item {i + 1}</Text>
18
+ </Card>
19
+ ))}
20
+ </Grid>
21
+ </View>
22
+
23
+ {/* Responsive Columns */}
24
+ <View gap="md">
25
+ <Text typography="subtitle1">Responsive Columns</Text>
26
+ <Text typography="caption" color="secondary">
27
+ 1 col on mobile, 2 on tablet, 4 on desktop
28
+ </Text>
29
+ <Grid columns={{ xs: 1, sm: 2, lg: 4 }} gap="md">
30
+ {Array.from({ length: 8 }, (_, i) => (
31
+ <Card key={i} type="elevated" padding="md">
32
+ <Text weight="medium">Card {i + 1}</Text>
33
+ <Text typography="caption" color="secondary">Responsive</Text>
34
+ </Card>
35
+ ))}
36
+ </Grid>
37
+ </View>
38
+
39
+ {/* Gap Variants */}
40
+ <View gap="md">
41
+ <Text typography="subtitle1">Gap Variants</Text>
42
+
43
+ <Text typography="caption" color="secondary">gap="xs"</Text>
44
+ <Grid columns={4} gap="xs">
45
+ {Array.from({ length: 4 }, (_, i) => (
46
+ <Card key={i} type="outlined" padding="sm">
47
+ <Text typography="caption">XS</Text>
48
+ </Card>
49
+ ))}
50
+ </Grid>
51
+
52
+ <Text typography="caption" color="secondary">gap="md"</Text>
53
+ <Grid columns={4} gap="md">
54
+ {Array.from({ length: 4 }, (_, i) => (
55
+ <Card key={i} type="outlined" padding="sm">
56
+ <Text typography="caption">MD</Text>
57
+ </Card>
58
+ ))}
59
+ </Grid>
60
+
61
+ <Text typography="caption" color="secondary">gap="xl"</Text>
62
+ <Grid columns={4} gap="xl">
63
+ {Array.from({ length: 4 }, (_, i) => (
64
+ <Card key={i} type="outlined" padding="sm">
65
+ <Text typography="caption">XL</Text>
66
+ </Card>
67
+ ))}
68
+ </Grid>
69
+ </View>
70
+
71
+ {/* Dashboard Layout */}
72
+ <View gap="md">
73
+ <Text typography="subtitle1">Dashboard Layout</Text>
74
+ <Grid columns={{ xs: 1, md: 2, lg: 4 }} gap="md">
75
+ <Card type="elevated" padding="md">
76
+ <Text typography="caption" color="secondary">Revenue</Text>
77
+ <Text typography="h3" weight="bold">$12,345</Text>
78
+ </Card>
79
+ <Card type="elevated" padding="md">
80
+ <Text typography="caption" color="secondary">Users</Text>
81
+ <Text typography="h3" weight="bold">1,234</Text>
82
+ </Card>
83
+ <Card type="elevated" padding="md">
84
+ <Text typography="caption" color="secondary">Orders</Text>
85
+ <Text typography="h3" weight="bold">567</Text>
86
+ </Card>
87
+ <Card type="elevated" padding="md">
88
+ <Text typography="caption" color="secondary">Growth</Text>
89
+ <Text typography="h3" weight="bold">+12%</Text>
90
+ </Card>
91
+ </Grid>
92
+ </View>
93
+
94
+ {/* Grid with Padding */}
95
+ <View gap="md">
96
+ <Text typography="subtitle1">Grid with Padding</Text>
97
+ <Grid columns={2} gap="sm" padding="lg">
98
+ {Array.from({ length: 4 }, (_, i) => (
99
+ <Card key={i} type="outlined" padding="md">
100
+ <Text>Padded Item {i + 1}</Text>
101
+ </Card>
102
+ ))}
103
+ </Grid>
104
+ </View>
105
+ </View>
106
+ </Screen>
107
+ );
108
+ };
@@ -32,4 +32,5 @@ export { AlertExamples } from './AlertExamples';
32
32
  export { SkeletonExamples } from './SkeletonExamples';
33
33
  export { ChipExamples } from './ChipExamples';
34
34
  export { BreadcrumbExamples } from './BreadcrumbExamples';
35
+ export { GridExamples } from './GridExamples';
35
36
  export { ThemeExtensionExamples } from './ThemeExtensionExamples';
@@ -89,6 +89,9 @@ export * from './Table/types';
89
89
  export { default as Menu } from './Menu';
90
90
  export * from './Menu/types';
91
91
 
92
+ export { default as Grid } from './Grid';
93
+ export * from './Grid/types';
94
+
92
95
  export { default as Image } from './Image';
93
96
  export * from './Image/types';
94
97
 
@@ -144,6 +147,7 @@ export type { AccordionProps, AccordionItem } from './Accordion/types';
144
147
  export type { ListProps, ListItemProps, ListSectionProps } from './List/types';
145
148
  export type { TableProps, TableColumn } from './Table/types';
146
149
  export type { MenuProps, MenuItem } from './Menu/types';
150
+ export type { GridProps, ResponsiveColumns, GridGap } from './Grid/types';
147
151
  export type { ImageProps } from './Image/types';
148
152
  export type { VideoProps, VideoSource } from './Video/types';
149
153
  export type { AlertProps } from './Alert/types';
package/src/index.ts CHANGED
@@ -100,6 +100,9 @@ export * from './Table/types';
100
100
  export { default as Menu } from './Menu';
101
101
  export * from './Menu/types';
102
102
 
103
+ export { default as Grid } from './Grid';
104
+ export * from './Grid/types';
105
+
103
106
  export { default as Image } from './Image';
104
107
  export * from './Image/types';
105
108
 
@@ -154,6 +157,7 @@ export type { AccordionProps, AccordionItem } from './Accordion/types';
154
157
  export type { ListProps, ListItemProps, ListSectionProps } from './List/types';
155
158
  export type { TableProps, TableColumn } from './Table/types';
156
159
  export type { MenuProps, MenuItem } from './Menu/types';
160
+ export type { GridProps, ResponsiveColumns, GridGap } from './Grid/types';
157
161
  export type { ImageProps } from './Image/types';
158
162
  export type { VideoProps, VideoSource } from './Video/types';
159
163
  export type { AlertProps } from './Alert/types';