@utilitywarehouse/hearth-react-native 0.27.2 → 0.28.0-testid-fix-1
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/.turbo/turbo-build.log +5 -4
- package/.turbo/turbo-lint.log +70 -69
- package/CHANGELOG.md +149 -0
- package/build/components/Button/ButtonRoot.js +8 -0
- package/build/components/Combobox/Combobox.context.d.ts +13 -0
- package/build/components/Combobox/Combobox.context.js +9 -0
- package/build/components/Combobox/Combobox.d.ts +6 -0
- package/build/components/Combobox/Combobox.js +246 -0
- package/build/components/Combobox/Combobox.props.d.ts +180 -0
- package/build/components/Combobox/Combobox.props.js +1 -0
- package/build/components/Combobox/ComboboxOption.d.ts +6 -0
- package/build/components/Combobox/ComboboxOption.js +56 -0
- package/build/components/Combobox/index.d.ts +4 -0
- package/build/components/Combobox/index.js +3 -0
- package/build/components/DatePicker/TimePicker.d.ts +3 -0
- package/build/components/DatePicker/TimePicker.js +84 -0
- package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
- package/build/components/DatePicker/time-picker/animated-math.js +19 -0
- package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-native.js +17 -0
- package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-picker.js +10 -0
- package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
- package/build/components/DatePicker/time-picker/period-web.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
- package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
- package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
- package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
- package/build/components/DatePicker/time-picker/wheel.js +10 -0
- package/build/components/List/List.js +2 -2
- package/build/components/Modal/Modal.js +31 -42
- package/build/components/Modal/Modal.web.js +3 -3
- package/build/components/Pagination/Pagination.d.ts +6 -0
- package/build/components/Pagination/Pagination.js +125 -0
- package/build/components/Pagination/Pagination.props.d.ts +26 -0
- package/build/components/Pagination/Pagination.props.js +1 -0
- package/build/components/Pagination/Pagination.utils.d.ts +2 -0
- package/build/components/Pagination/Pagination.utils.js +20 -0
- package/build/components/Pagination/Pagination.utils.test.d.ts +1 -0
- package/build/components/Pagination/Pagination.utils.test.js +16 -0
- package/build/components/Pagination/index.d.ts +2 -0
- package/build/components/Pagination/index.js +1 -0
- package/build/components/SafeAreaView/SafeAreaView.d.ts +5 -0
- package/build/components/SafeAreaView/SafeAreaView.js +117 -0
- package/build/components/SafeAreaView/SafeAreaView.props.d.ts +17 -0
- package/build/components/SafeAreaView/SafeAreaView.props.js +1 -0
- package/build/components/SafeAreaView/index.d.ts +2 -0
- package/build/components/SafeAreaView/index.js +1 -0
- package/build/components/Select/Select.d.ts +1 -1
- package/build/components/Select/Select.js +6 -5
- package/build/components/Select/Select.props.d.ts +4 -0
- package/build/components/Select/SelectOption.d.ts +1 -1
- package/build/components/Select/SelectOption.js +2 -2
- package/build/components/Table/Table.context.d.ts +12 -0
- package/build/components/Table/Table.context.js +9 -0
- package/build/components/Table/Table.d.ts +6 -0
- package/build/components/Table/Table.js +71 -0
- package/build/components/Table/Table.props.d.ts +56 -0
- package/build/components/Table/Table.props.js +1 -0
- package/build/components/Table/Table.utils.d.ts +5 -0
- package/build/components/Table/Table.utils.js +48 -0
- package/build/components/Table/Table.utils.test.d.ts +1 -0
- package/build/components/Table/Table.utils.test.js +71 -0
- package/build/components/Table/TableBody.d.ts +6 -0
- package/build/components/Table/TableBody.js +16 -0
- package/build/components/Table/TableCell.d.ts +10 -0
- package/build/components/Table/TableCell.js +44 -0
- package/build/components/Table/TableHeader.d.ts +6 -0
- package/build/components/Table/TableHeader.js +24 -0
- package/build/components/Table/TableHeaderCell.d.ts +10 -0
- package/build/components/Table/TableHeaderCell.js +97 -0
- package/build/components/Table/TablePagination.d.ts +6 -0
- package/build/components/Table/TablePagination.js +7 -0
- package/build/components/Table/TableRow.d.ts +8 -0
- package/build/components/Table/TableRow.js +25 -0
- package/build/components/Table/index.d.ts +8 -0
- package/build/components/Table/index.js +7 -0
- package/build/components/Timeline/Timeline.d.ts +6 -0
- package/build/components/Timeline/Timeline.js +34 -0
- package/build/components/Timeline/Timeline.props.d.ts +47 -0
- package/build/components/Timeline/Timeline.props.js +1 -0
- package/build/components/Timeline/TimelineItem.d.ts +6 -0
- package/build/components/Timeline/TimelineItem.js +235 -0
- package/build/components/Timeline/index.d.ts +3 -0
- package/build/components/Timeline/index.js +2 -0
- package/build/components/VerificationInput/VerificationInput.js +3 -3
- package/build/components/index.d.ts +5 -0
- package/build/components/index.js +5 -0
- package/build/tokens/components/dark/timeline.d.ts +2 -2
- package/build/tokens/components/dark/timeline.js +2 -2
- package/docs/components/AllComponents.web.tsx +106 -23
- package/docs/llm-docs/unistyles-llms-full.txt +1132 -534
- package/docs/llm-docs/unistyles-llms-small.txt +37 -37
- package/package.json +4 -4
- package/src/components/Button/Button.stories.tsx +43 -7
- package/src/components/Button/ButtonRoot.tsx +8 -0
- package/src/components/Combobox/Combobox.context.ts +26 -0
- package/src/components/Combobox/Combobox.docs.mdx +277 -0
- package/src/components/Combobox/Combobox.figma.tsx +60 -0
- package/src/components/Combobox/Combobox.props.ts +187 -0
- package/src/components/Combobox/Combobox.stories.tsx +233 -0
- package/src/components/Combobox/Combobox.tsx +446 -0
- package/src/components/Combobox/ComboboxOption.tsx +100 -0
- package/src/components/Combobox/index.ts +9 -0
- package/src/components/List/List.tsx +5 -4
- package/src/components/Modal/Modal.tsx +67 -74
- package/src/components/Modal/Modal.web.tsx +3 -3
- package/src/components/Pagination/Pagination.docs.mdx +99 -0
- package/src/components/Pagination/Pagination.figma.tsx +20 -0
- package/src/components/Pagination/Pagination.props.ts +28 -0
- package/src/components/Pagination/Pagination.stories.tsx +88 -0
- package/src/components/Pagination/Pagination.tsx +248 -0
- package/src/components/Pagination/Pagination.utils.test.ts +20 -0
- package/src/components/Pagination/Pagination.utils.ts +37 -0
- package/src/components/Pagination/index.ts +2 -0
- package/src/components/SafeAreaView/SafeAreaView.props.ts +20 -0
- package/src/components/SafeAreaView/SafeAreaView.tsx +173 -0
- package/src/components/SafeAreaView/index.ts +2 -0
- package/src/components/Select/Select.props.ts +4 -0
- package/src/components/Select/Select.tsx +35 -28
- package/src/components/Select/SelectOption.tsx +2 -0
- package/src/components/Table/Table.context.tsx +23 -0
- package/src/components/Table/Table.docs.mdx +239 -0
- package/src/components/Table/Table.figma.tsx +65 -0
- package/src/components/Table/Table.props.ts +65 -0
- package/src/components/Table/Table.stories.tsx +399 -0
- package/src/components/Table/Table.tsx +127 -0
- package/src/components/Table/Table.utils.test.ts +82 -0
- package/src/components/Table/Table.utils.ts +72 -0
- package/src/components/Table/TableBody.tsx +25 -0
- package/src/components/Table/TableCell.tsx +67 -0
- package/src/components/Table/TableHeader.tsx +41 -0
- package/src/components/Table/TableHeaderCell.tsx +136 -0
- package/src/components/Table/TablePagination.tsx +10 -0
- package/src/components/Table/TableRow.tsx +42 -0
- package/src/components/Table/index.ts +16 -0
- package/src/components/Timeline/Timeline.docs.mdx +177 -0
- package/src/components/Timeline/Timeline.figma.tsx +89 -0
- package/src/components/Timeline/Timeline.props.ts +51 -0
- package/src/components/Timeline/Timeline.stories.tsx +102 -0
- package/src/components/Timeline/Timeline.tsx +48 -0
- package/src/components/Timeline/TimelineItem.tsx +293 -0
- package/src/components/Timeline/index.ts +9 -0
- package/src/components/VerificationInput/VerificationInput.tsx +3 -0
- package/src/components/index.ts +5 -0
- package/src/tokens/components/dark/timeline.ts +2 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ViewProps } from 'react-native';
|
|
2
|
+
export interface PaginationProps extends ViewProps {
|
|
3
|
+
/**
|
|
4
|
+
* The current active page number (1-indexed).
|
|
5
|
+
*/
|
|
6
|
+
currentPage: number;
|
|
7
|
+
/**
|
|
8
|
+
* The total number of pages.
|
|
9
|
+
*/
|
|
10
|
+
totalPages: number;
|
|
11
|
+
/**
|
|
12
|
+
* Callback fired when the page changes.
|
|
13
|
+
*/
|
|
14
|
+
onPageChange: (page: number) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to show condensed copy instead of individual page items.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
condensed?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to hide the first and last page controls.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
hideSkipButtons?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export default PaginationProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const ELLIPSIS = '...';
|
|
2
|
+
const MAX_VISIBLE_ITEMS = 7;
|
|
3
|
+
export const generatePageNumbers = (currentPage, totalPages) => {
|
|
4
|
+
if (totalPages <= MAX_VISIBLE_ITEMS) {
|
|
5
|
+
return Array.from({ length: totalPages }, (_, index) => index + 1);
|
|
6
|
+
}
|
|
7
|
+
const pages = [];
|
|
8
|
+
const isNearStart = currentPage <= 4;
|
|
9
|
+
const isNearEnd = currentPage > totalPages - 4;
|
|
10
|
+
if (isNearStart) {
|
|
11
|
+
pages.push(1, 2, 3, 4, 5, ELLIPSIS, totalPages);
|
|
12
|
+
return pages;
|
|
13
|
+
}
|
|
14
|
+
if (isNearEnd) {
|
|
15
|
+
pages.push(1, ELLIPSIS, totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
|
|
16
|
+
return pages;
|
|
17
|
+
}
|
|
18
|
+
pages.push(1, ELLIPSIS, currentPage - 1, currentPage, currentPage + 1, ELLIPSIS, totalPages);
|
|
19
|
+
return pages;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ELLIPSIS, generatePageNumbers } from './Pagination.utils';
|
|
3
|
+
describe('generatePageNumbers', () => {
|
|
4
|
+
it('returns all pages when the total fits within the visible limit', () => {
|
|
5
|
+
expect(generatePageNumbers(1, 7)).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
|
6
|
+
});
|
|
7
|
+
it('returns the leading window when the current page is near the start', () => {
|
|
8
|
+
expect(generatePageNumbers(2, 10)).toEqual([1, 2, 3, 4, 5, ELLIPSIS, 10]);
|
|
9
|
+
});
|
|
10
|
+
it('returns the centered window when the current page is in the middle', () => {
|
|
11
|
+
expect(generatePageNumbers(5, 10)).toEqual([1, ELLIPSIS, 4, 5, 6, ELLIPSIS, 10]);
|
|
12
|
+
});
|
|
13
|
+
it('returns the trailing window when the current page is near the end', () => {
|
|
14
|
+
expect(generatePageNumbers(9, 10)).toEqual([1, ELLIPSIS, 6, 7, 8, 9, 10]);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Pagination } from './Pagination';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { StyleSheet as RNStyleSheet, useWindowDimensions, View, } from 'react-native';
|
|
4
|
+
import { UnistylesRuntime } from '../../core';
|
|
5
|
+
const DEFAULT_EDGES = ['top', 'right', 'bottom', 'left'];
|
|
6
|
+
const EMPTY_INSETS = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
7
|
+
const EDGE_EPSILON = 1;
|
|
8
|
+
const getNumericStyleValue = (value) => {
|
|
9
|
+
return typeof value === 'number' ? value : 0;
|
|
10
|
+
};
|
|
11
|
+
const getStyleInsetValue = (style, mode, edge) => {
|
|
12
|
+
const prefix = mode === 'margin' ? 'margin' : 'padding';
|
|
13
|
+
if (!style) {
|
|
14
|
+
// No style specified at all; treat as zero inset for safe-area calculations.
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
if (edge === 'top') {
|
|
18
|
+
const raw = style[`${prefix}Top`] ?? style[`${prefix}Vertical`] ?? style[prefix];
|
|
19
|
+
if (raw == null) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
return getNumericStyleValue(raw);
|
|
23
|
+
}
|
|
24
|
+
if (edge === 'bottom') {
|
|
25
|
+
const raw = style[`${prefix}Bottom`] ?? style[`${prefix}Vertical`] ?? style[prefix];
|
|
26
|
+
if (raw == null) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
return getNumericStyleValue(raw);
|
|
30
|
+
}
|
|
31
|
+
if (edge === 'left') {
|
|
32
|
+
const raw = style[`${prefix}Left`] ?? style[`${prefix}Horizontal`] ?? style[prefix];
|
|
33
|
+
if (raw == null) {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
return getNumericStyleValue(raw);
|
|
37
|
+
}
|
|
38
|
+
const raw = style[`${prefix}Right`] ?? style[`${prefix}Horizontal`] ?? style[prefix];
|
|
39
|
+
if (raw == null) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
return getNumericStyleValue(raw);
|
|
43
|
+
};
|
|
44
|
+
const SafeAreaView = forwardRef(({ children, edges = DEFAULT_EDGES, mode = 'padding', onLayout, style, ...props }, ref) => {
|
|
45
|
+
const viewRef = useRef(null);
|
|
46
|
+
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
47
|
+
const [edgeInsets, setEdgeInsets] = useState(EMPTY_INSETS);
|
|
48
|
+
const flattenedStyle = useMemo(() => RNStyleSheet.flatten(style), [style]);
|
|
49
|
+
const updateEdgeInsets = useCallback(() => {
|
|
50
|
+
const currentView = viewRef.current;
|
|
51
|
+
if (!currentView) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
currentView.measureInWindow((x, y, width, height) => {
|
|
55
|
+
const runtimeInsets = UnistylesRuntime.insets ?? EMPTY_INSETS;
|
|
56
|
+
const nextEdgeInsets = {
|
|
57
|
+
top: edges.includes('top') ? Math.max(runtimeInsets.top - Math.max(y, 0), 0) : 0,
|
|
58
|
+
right: edges.includes('right')
|
|
59
|
+
? Math.max(runtimeInsets.right - Math.max(windowWidth - (x + width), 0), 0)
|
|
60
|
+
: 0,
|
|
61
|
+
bottom: edges.includes('bottom')
|
|
62
|
+
? Math.max(runtimeInsets.bottom - Math.max(windowHeight - (y + height), 0), 0)
|
|
63
|
+
: 0,
|
|
64
|
+
left: edges.includes('left') ? Math.max(runtimeInsets.left - Math.max(x, 0), 0) : 0,
|
|
65
|
+
};
|
|
66
|
+
setEdgeInsets(previousInsets => {
|
|
67
|
+
const hasChanged = Object.keys(nextEdgeInsets).some(edge => Math.abs(previousInsets[edge] - nextEdgeInsets[edge]) > EDGE_EPSILON);
|
|
68
|
+
return hasChanged ? nextEdgeInsets : previousInsets;
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}, [edges, windowHeight, windowWidth]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const frame = requestAnimationFrame(updateEdgeInsets);
|
|
74
|
+
return () => cancelAnimationFrame(frame);
|
|
75
|
+
}, [updateEdgeInsets]);
|
|
76
|
+
const handleRef = useCallback((node) => {
|
|
77
|
+
viewRef.current = node;
|
|
78
|
+
if (typeof ref === 'function') {
|
|
79
|
+
ref(node);
|
|
80
|
+
}
|
|
81
|
+
else if (ref) {
|
|
82
|
+
ref.current = node;
|
|
83
|
+
}
|
|
84
|
+
}, [ref]);
|
|
85
|
+
const handleLayout = useCallback((event) => {
|
|
86
|
+
onLayout?.(event);
|
|
87
|
+
requestAnimationFrame(updateEdgeInsets);
|
|
88
|
+
}, [onLayout, updateEdgeInsets]);
|
|
89
|
+
const safeAreaStyle = useMemo(() => {
|
|
90
|
+
const nextStyle = {};
|
|
91
|
+
if (mode === 'padding') {
|
|
92
|
+
nextStyle.paddingTop = getStyleInsetValue(flattenedStyle, mode, 'top') + edgeInsets.top;
|
|
93
|
+
nextStyle.paddingRight =
|
|
94
|
+
getStyleInsetValue(flattenedStyle, mode, 'right') + edgeInsets.right;
|
|
95
|
+
nextStyle.paddingBottom =
|
|
96
|
+
getStyleInsetValue(flattenedStyle, mode, 'bottom') + edgeInsets.bottom;
|
|
97
|
+
nextStyle.paddingLeft = getStyleInsetValue(flattenedStyle, mode, 'left') + edgeInsets.left;
|
|
98
|
+
return nextStyle;
|
|
99
|
+
}
|
|
100
|
+
nextStyle.marginTop = getStyleInsetValue(flattenedStyle, mode, 'top') + edgeInsets.top;
|
|
101
|
+
nextStyle.marginRight = getStyleInsetValue(flattenedStyle, mode, 'right') + edgeInsets.right;
|
|
102
|
+
nextStyle.marginBottom =
|
|
103
|
+
getStyleInsetValue(flattenedStyle, mode, 'bottom') + edgeInsets.bottom;
|
|
104
|
+
nextStyle.marginLeft = getStyleInsetValue(flattenedStyle, mode, 'left') + edgeInsets.left;
|
|
105
|
+
return nextStyle;
|
|
106
|
+
}, [
|
|
107
|
+
edgeInsets.bottom,
|
|
108
|
+
edgeInsets.left,
|
|
109
|
+
edgeInsets.right,
|
|
110
|
+
edgeInsets.top,
|
|
111
|
+
flattenedStyle,
|
|
112
|
+
mode,
|
|
113
|
+
]);
|
|
114
|
+
return (_jsx(View, { ref: handleRef, onLayout: handleLayout, style: [style, safeAreaStyle], ...props, children: children }));
|
|
115
|
+
});
|
|
116
|
+
SafeAreaView.displayName = 'SafeAreaView';
|
|
117
|
+
export default SafeAreaView;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ViewProps } from 'react-native';
|
|
2
|
+
export type SafeAreaEdge = 'top' | 'right' | 'bottom' | 'left';
|
|
3
|
+
interface SafeAreaViewProps extends ViewProps {
|
|
4
|
+
/**
|
|
5
|
+
* Which edges should receive safe area compensation.
|
|
6
|
+
*
|
|
7
|
+
* @default ['top', 'right', 'bottom', 'left']
|
|
8
|
+
*/
|
|
9
|
+
edges?: SafeAreaEdge[];
|
|
10
|
+
/**
|
|
11
|
+
* Whether safe area compensation should be applied as padding or margin.
|
|
12
|
+
*
|
|
13
|
+
* @default 'padding'
|
|
14
|
+
*/
|
|
15
|
+
mode?: 'padding' | 'margin';
|
|
16
|
+
}
|
|
17
|
+
export default SafeAreaViewProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as SafeAreaView } from './SafeAreaView';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import SelectProps from './Select.props';
|
|
2
2
|
declare const Select: {
|
|
3
|
-
({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, helperText, helperIcon, invalidText, validText, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, helperText, helperIcon, invalidText, validText, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, testID, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default Select;
|
|
@@ -11,7 +11,8 @@ import { Icon } from '../Icon';
|
|
|
11
11
|
import { Input } from '../Input';
|
|
12
12
|
import { SelectContext } from './Select.context';
|
|
13
13
|
import SelectOption from './SelectOption';
|
|
14
|
-
|
|
14
|
+
import { SafeAreaView } from '../SafeAreaView';
|
|
15
|
+
const Select = ({ options = [], value, onValueChange, label, labelVariant = 'body', placeholder = 'Select an option', disabled = false, leadingIcon: LeadingIcon, validationStatus = 'initial', helperText, helperIcon, invalidText, validText, required = true, children, bottomSheetProps, menuHeading, readonly = false, emptyText = 'No options available', listProps, searchable = false, searchPlaceholder = 'Search', testID, ...rest }) => {
|
|
15
16
|
const formFieldContext = useFormFieldContext();
|
|
16
17
|
const validationStatusFromContext = formFieldContext?.validationStatus ?? validationStatus;
|
|
17
18
|
const isRequired = formFieldContext?.required ?? required;
|
|
@@ -64,19 +65,19 @@ const Select = ({ options = [], value, onValueChange, label, labelVariant = 'bod
|
|
|
64
65
|
setIsOpen(false);
|
|
65
66
|
setSearch('');
|
|
66
67
|
}, []);
|
|
67
|
-
const renderSelectOption = useCallback(({ item }) => (_jsx(SelectOption, { label: item.label, value: item.value, disabled: item.disabled, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon })), []);
|
|
68
|
+
const renderSelectOption = useCallback(({ item }) => (_jsx(SelectOption, { label: item.label, value: item.value, disabled: item.disabled, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon, testID: testID ? `${testID}-option-${item.label}` : undefined })), [testID]);
|
|
68
69
|
const renderEmptyComponent = useCallback(() => (_jsx(BottomSheetView, { style: styles.emptyContainer, children: _jsx(DetailText, { children: emptyText }) })), [emptyText]);
|
|
69
|
-
return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatusFromContext, required: isRequired, disabled: isDisabled, readonly: isReadonly, invalidText: invalidText, validText: validText, children: _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, style: ({ pressed }) => [
|
|
70
|
+
return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatusFromContext, required: isRequired, disabled: isDisabled, readonly: isReadonly, invalidText: invalidText, validText: validText, children: _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, testID: testID, style: ({ pressed }) => [
|
|
70
71
|
styles.selectContainer,
|
|
71
72
|
styles.pressedContainer(pressed || isOpen),
|
|
72
73
|
], children: [!!LeadingIcon && (_jsx(View, { children: (() => {
|
|
73
74
|
const IconAny = Icon;
|
|
74
75
|
return _jsx(IconAny, { as: LeadingIcon, style: styles.icon });
|
|
75
|
-
})() })), _jsx(View, { style: styles.optionContainer, children: _jsx(BodyText, { numberOfLines: 1, style: styles.placeholderText, children: selectedOption?.label || selectedLabel || placeholder }) }), _jsx(View, { children: _jsx(Icon, { as: ExpandSmallIcon, style: styles.icon }) })] }) }), _jsx(BottomSheetModal, { ref: bottomSheetModalRef, snapPoints: ['25%', '40%', '80%'], onChange: handleClose, enableDynamicSizing: false, ...bottomSheetProps, children:
|
|
76
|
+
})() })), _jsx(View, { style: styles.optionContainer, children: _jsx(BodyText, { numberOfLines: 1, style: styles.placeholderText, children: selectedOption?.label || selectedLabel || placeholder }) }), _jsx(View, { children: _jsx(Icon, { as: ExpandSmallIcon, style: styles.icon }) })] }) }), _jsx(BottomSheetModal, { ref: bottomSheetModalRef, snapPoints: ['25%', '40%', '80%'], onChange: handleClose, enableDynamicSizing: false, ...bottomSheetProps, children: _jsx(SelectContext.Provider, { value: {
|
|
76
77
|
selectedValue: value,
|
|
77
78
|
onValueChange,
|
|
78
79
|
close: closeBottomSheet,
|
|
79
|
-
}, children: [menuHeading && (_jsx(View, { style: styles.headingContainer, children: _jsx(DetailText, { size: "lg", children: menuHeading }) })), searchable && (_jsx(View, { style: styles.searchContainer, children: _jsx(Input, { placeholder: searchPlaceholder, value: search, inBottomSheet: true, onChangeText: setSearch, type: "search" }) })), children ? (_jsx(BottomSheetScrollView, { children: children })) : (_jsx(BottomSheetFlatList, { data: filteredOptions, keyExtractor: (option) => option.value, renderItem: renderSelectOption, ListEmptyComponent: renderEmptyComponent, ...listProps }))] }) })] }));
|
|
80
|
+
}, children: _jsxs(SafeAreaView, { edges: ['top'], style: { flex: 1 }, children: [menuHeading && (_jsx(View, { style: styles.headingContainer, children: _jsx(DetailText, { size: "lg", children: menuHeading }) })), searchable && (_jsx(View, { style: styles.searchContainer, children: _jsx(Input, { placeholder: searchPlaceholder, value: search, inBottomSheet: true, onChangeText: setSearch, type: "search", testID: testID ? `${testID}-search` : undefined }) })), children ? (_jsx(BottomSheetScrollView, { children: children })) : (_jsx(BottomSheetFlatList, { data: filteredOptions, keyExtractor: (option) => option.value, renderItem: renderSelectOption, ListEmptyComponent: renderEmptyComponent, ...listProps }))] }) }) })] }));
|
|
80
81
|
};
|
|
81
82
|
const styles = StyleSheet.create(theme => ({
|
|
82
83
|
container: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SelectOptionProps } from './Select.props';
|
|
2
2
|
declare const SelectOption: {
|
|
3
|
-
({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, }: SelectOptionProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, testID, }: SelectOptionProps): import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
displayName: string;
|
|
5
5
|
};
|
|
6
6
|
export default SelectOption;
|
|
@@ -5,7 +5,7 @@ import { StyleSheet } from 'react-native-unistyles';
|
|
|
5
5
|
import { BodyText } from '../BodyText';
|
|
6
6
|
import { Icon } from '../Icon';
|
|
7
7
|
import { useSelectContext } from './Select.context';
|
|
8
|
-
const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, }) => {
|
|
8
|
+
const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: RightIcon, selected, disabled, onPress, testID, }) => {
|
|
9
9
|
const { selectedValue, onValueChange, close } = useSelectContext();
|
|
10
10
|
const isSelected = selected !== undefined ? selected : selectedValue === value;
|
|
11
11
|
styles.useVariants({ disabled });
|
|
@@ -22,7 +22,7 @@ const SelectOption = ({ label, value, leadingIcon: LeftIcon, trailingIcon: Right
|
|
|
22
22
|
close();
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
return (_jsxs(Pressable, { onPress: handlePress, disabled: disabled, style: ({ pressed }) => [styles.container, pressed && styles.pressed], children: [!!LeftIcon && (_jsx(View, { children: _jsx(Icon, { as: LeftIcon, style: styles.icon }) })), _jsx(View, { style: styles.labelContainer, children: _jsx(BodyText, { children: label }) }), isSelected && (_jsx(View, { children: _jsx(Icon, { as: TickSmallIcon, style: styles.icon }) })), !!RightIcon && !isSelected && (_jsx(View, { children: _jsx(Icon, { as: RightIcon, style: styles.icon }) }))] }));
|
|
25
|
+
return (_jsxs(Pressable, { onPress: handlePress, disabled: disabled, testID: testID, style: ({ pressed }) => [styles.container, pressed && styles.pressed], children: [!!LeftIcon && (_jsx(View, { children: _jsx(Icon, { as: LeftIcon, style: styles.icon }) })), _jsx(View, { style: styles.labelContainer, children: _jsx(BodyText, { children: label }) }), isSelected && (_jsx(View, { children: _jsx(Icon, { as: TickSmallIcon, style: styles.icon }) })), !!RightIcon && !isSelected && (_jsx(View, { children: _jsx(Icon, { as: RightIcon, style: styles.icon }) }))] }));
|
|
26
26
|
};
|
|
27
27
|
const styles = StyleSheet.create(theme => ({
|
|
28
28
|
container: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TableProps } from './Table.props';
|
|
2
|
+
type TableContainer = TableProps['container'];
|
|
3
|
+
type TableColumnWidths = NonNullable<TableProps['columnWidths']>;
|
|
4
|
+
interface TableContextValue {
|
|
5
|
+
columnWidths: TableColumnWidths;
|
|
6
|
+
container: TableContainer;
|
|
7
|
+
columnCount: number;
|
|
8
|
+
hasPagination: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const TableContextProvider: import("react").Provider<TableContextValue>;
|
|
11
|
+
export declare const useTableContext: () => TableContextValue;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
const TableContext = createContext({
|
|
3
|
+
columnWidths: [],
|
|
4
|
+
container: 'none',
|
|
5
|
+
columnCount: 1,
|
|
6
|
+
hasPagination: false,
|
|
7
|
+
});
|
|
8
|
+
export const TableContextProvider = TableContext.Provider;
|
|
9
|
+
export const useTableContext = () => useContext(TableContext);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Children, isValidElement, useState } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { ScrollView } from 'react-native-gesture-handler';
|
|
5
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
6
|
+
import { Card } from '../Card';
|
|
7
|
+
import { TableContextProvider } from './Table.context';
|
|
8
|
+
import { getMinTableWidth } from './Table.utils';
|
|
9
|
+
const MIN_COLUMN_WIDTH = 120;
|
|
10
|
+
const getChildCount = (node) => {
|
|
11
|
+
if (!isValidElement(node)) {
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
const childProps = node.props;
|
|
15
|
+
return Children.count(childProps.children);
|
|
16
|
+
};
|
|
17
|
+
const getMaxColumnCount = (children) => {
|
|
18
|
+
let maxColumns = 1;
|
|
19
|
+
Children.forEach(children, child => {
|
|
20
|
+
if (!isValidElement(child)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const childType = child.type;
|
|
24
|
+
const childProps = child.props;
|
|
25
|
+
if (childType.displayName === 'TableHeader') {
|
|
26
|
+
maxColumns = Math.max(maxColumns, getChildCount(child));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (childType.displayName === 'TableBody') {
|
|
30
|
+
Children.forEach(childProps.children, rowChild => {
|
|
31
|
+
maxColumns = Math.max(maxColumns, getChildCount(rowChild));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return maxColumns;
|
|
36
|
+
};
|
|
37
|
+
const Table = ({ children, columnWidths = [], container = 'none', pagination, style, ...props }) => {
|
|
38
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
39
|
+
const columnCount = getMaxColumnCount(children);
|
|
40
|
+
const minTableWidth = getMinTableWidth(columnCount, columnWidths, MIN_COLUMN_WIDTH);
|
|
41
|
+
const tableWidth = Math.max(containerWidth, minTableWidth);
|
|
42
|
+
const isScrollable = tableWidth > containerWidth + 1;
|
|
43
|
+
const handleLayout = (event) => {
|
|
44
|
+
const nextWidth = event.nativeEvent.layout.width;
|
|
45
|
+
setContainerWidth(currentWidth => (currentWidth === nextWidth ? currentWidth : nextWidth));
|
|
46
|
+
};
|
|
47
|
+
const content = (_jsxs(View, { onLayout: handleLayout, style: styles.wrapper, children: [_jsx(ScrollView, { alwaysBounceHorizontal: false, alwaysBounceVertical: false, bounces: false, canCancelContentTouches: true, contentContainerStyle: styles.scrollContent, horizontal: true, keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, overScrollMode: "never", scrollEnabled: isScrollable, scrollEventThrottle: 16, showsHorizontalScrollIndicator: true, children: _jsx(View, { style: [styles.content, { width: tableWidth || minTableWidth }], children: children }) }), pagination ? _jsx(View, { style: styles.pagination, children: pagination }) : null] }));
|
|
48
|
+
const hasPagination = Boolean(pagination);
|
|
49
|
+
return (_jsx(TableContextProvider, { value: { columnCount, columnWidths, container, hasPagination }, children: container !== 'none' ? (_jsx(Card, { ...props, colorScheme: "neutralStrong", noPadding: true, style: style, variant: container, children: content })) : (_jsx(View, { ...props, style: [styles.plainTable, style], children: content })) }));
|
|
50
|
+
};
|
|
51
|
+
Table.displayName = 'Table';
|
|
52
|
+
const styles = StyleSheet.create(theme => ({
|
|
53
|
+
wrapper: {
|
|
54
|
+
width: '100%',
|
|
55
|
+
},
|
|
56
|
+
plainTable: {
|
|
57
|
+
width: '100%',
|
|
58
|
+
overflow: 'hidden',
|
|
59
|
+
},
|
|
60
|
+
scrollContent: {
|
|
61
|
+
minWidth: '100%',
|
|
62
|
+
},
|
|
63
|
+
content: {
|
|
64
|
+
alignSelf: 'flex-start',
|
|
65
|
+
},
|
|
66
|
+
pagination: {
|
|
67
|
+
paddingHorizontal: theme.components.table.cell.padding,
|
|
68
|
+
paddingVertical: theme.space[200],
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
export default Table;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import type PaginationProps from '../Pagination/Pagination.props';
|
|
4
|
+
export interface TableFlexibleColumnWidth {
|
|
5
|
+
flex: number;
|
|
6
|
+
}
|
|
7
|
+
export type TableColumnWidth = number | `${number}fr` | TableFlexibleColumnWidth;
|
|
8
|
+
export interface TableProps extends ViewProps {
|
|
9
|
+
/**
|
|
10
|
+
* Controls whether the table is wrapped in a card container.
|
|
11
|
+
* @default 'none'
|
|
12
|
+
*/
|
|
13
|
+
container?: 'emphasis' | 'subtle' | 'none';
|
|
14
|
+
/**
|
|
15
|
+
* Optional per-column sizing using fixed pixel widths or flexible fractions.
|
|
16
|
+
* Use numbers for fixed widths and values like `'2fr'` for flexible columns.
|
|
17
|
+
* Objects like `{ flex: 2 }` are also supported internally.
|
|
18
|
+
* Unspecified columns default to `1fr`.
|
|
19
|
+
*/
|
|
20
|
+
columnWidths?: TableColumnWidth[];
|
|
21
|
+
/**
|
|
22
|
+
* Optional pagination controls rendered beneath the rows.
|
|
23
|
+
*/
|
|
24
|
+
pagination?: React.ReactElement<PaginationProps> | ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export interface TableBodyProps extends ViewProps {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
export interface TableHeaderProps extends ViewProps {
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Visual style for header cells.
|
|
33
|
+
* @default 'purple'
|
|
34
|
+
*/
|
|
35
|
+
color?: 'purple' | 'white';
|
|
36
|
+
}
|
|
37
|
+
export interface TableRowProps extends ViewProps {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
}
|
|
40
|
+
export interface TableCellProps extends ViewProps {
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export interface TableHeaderCellProps extends ViewProps {
|
|
44
|
+
children?: ReactNode;
|
|
45
|
+
color?: 'purple' | 'white';
|
|
46
|
+
/**
|
|
47
|
+
* Renders the header cell as a row header within the body.
|
|
48
|
+
* @default false
|
|
49
|
+
*/
|
|
50
|
+
row?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Optional content aligned to the end of a header cell.
|
|
53
|
+
*/
|
|
54
|
+
trailingContent?: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
export type TablePaginationProps = PaginationProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { TableColumnWidth } from './Table.props';
|
|
3
|
+
export declare const getColumnMinWidth: (columnWidth: TableColumnWidth | undefined, minColumnWidth: number) => number;
|
|
4
|
+
export declare const getMinTableWidth: (columnCount: number, columnWidths: TableColumnWidth[], minColumnWidth: number) => number;
|
|
5
|
+
export declare const getColumnStyle: (columnWidth: TableColumnWidth | undefined, minColumnWidth: number) => ViewStyle;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const FR_UNIT_PATTERN = /^(\d*\.?\d+)fr$/;
|
|
2
|
+
const parseFractionUnit = (columnWidth) => {
|
|
3
|
+
if (typeof columnWidth !== 'string') {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
const match = columnWidth.match(FR_UNIT_PATTERN);
|
|
7
|
+
if (!match) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const parsedValue = Number(match[1]);
|
|
11
|
+
return Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : null;
|
|
12
|
+
};
|
|
13
|
+
const getFlexGrow = (columnWidth) => {
|
|
14
|
+
if (!columnWidth || typeof columnWidth === 'number') {
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
if (typeof columnWidth === 'string') {
|
|
18
|
+
return parseFractionUnit(columnWidth) ?? 1;
|
|
19
|
+
}
|
|
20
|
+
return Number.isFinite(columnWidth.flex) && columnWidth.flex > 0 ? columnWidth.flex : 1;
|
|
21
|
+
};
|
|
22
|
+
export const getColumnMinWidth = (columnWidth, minColumnWidth) => {
|
|
23
|
+
if (typeof columnWidth === 'number') {
|
|
24
|
+
return Math.max(columnWidth, minColumnWidth);
|
|
25
|
+
}
|
|
26
|
+
return minColumnWidth;
|
|
27
|
+
};
|
|
28
|
+
export const getMinTableWidth = (columnCount, columnWidths, minColumnWidth) => {
|
|
29
|
+
return Array.from({ length: columnCount }).reduce((totalWidth, _, index) => {
|
|
30
|
+
return totalWidth + getColumnMinWidth(columnWidths[index], minColumnWidth);
|
|
31
|
+
}, 0);
|
|
32
|
+
};
|
|
33
|
+
export const getColumnStyle = (columnWidth, minColumnWidth) => {
|
|
34
|
+
if (typeof columnWidth === 'number') {
|
|
35
|
+
return {
|
|
36
|
+
width: columnWidth,
|
|
37
|
+
minWidth: columnWidth,
|
|
38
|
+
flexGrow: 0,
|
|
39
|
+
flexShrink: 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
minWidth: minColumnWidth,
|
|
44
|
+
flexBasis: 0,
|
|
45
|
+
flexGrow: getFlexGrow(columnWidth),
|
|
46
|
+
flexShrink: 0,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getColumnMinWidth, getColumnStyle, getMinTableWidth } from './Table.utils';
|
|
3
|
+
describe('getColumnMinWidth', () => {
|
|
4
|
+
it('returns the configured fixed width when it is larger than the minimum', () => {
|
|
5
|
+
expect(getColumnMinWidth(180, 120)).toBe(180);
|
|
6
|
+
});
|
|
7
|
+
it('clamps fixed widths to the minimum column width', () => {
|
|
8
|
+
expect(getColumnMinWidth(80, 120)).toBe(120);
|
|
9
|
+
});
|
|
10
|
+
it('returns the minimum width for flexible and unspecified columns', () => {
|
|
11
|
+
expect(getColumnMinWidth('2fr', 120)).toBe(120);
|
|
12
|
+
expect(getColumnMinWidth({ flex: 3 }, 120)).toBe(120);
|
|
13
|
+
expect(getColumnMinWidth(undefined, 120)).toBe(120);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('getMinTableWidth', () => {
|
|
17
|
+
it('adds fixed widths and minimum widths for flexible columns', () => {
|
|
18
|
+
expect(getMinTableWidth(4, [180, '2fr', { flex: 3 }], 120)).toBe(540);
|
|
19
|
+
});
|
|
20
|
+
it('uses the minimum width for unspecified trailing columns', () => {
|
|
21
|
+
expect(getMinTableWidth(3, [200], 120)).toBe(440);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('getColumnStyle', () => {
|
|
25
|
+
it('returns a fixed-width style for numeric column widths', () => {
|
|
26
|
+
expect(getColumnStyle(180, 120)).toEqual({
|
|
27
|
+
width: 180,
|
|
28
|
+
minWidth: 180,
|
|
29
|
+
flexGrow: 0,
|
|
30
|
+
flexShrink: 0,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
it('maps fr values to flexGrow', () => {
|
|
34
|
+
expect(getColumnStyle('2fr', 120)).toEqual({
|
|
35
|
+
minWidth: 120,
|
|
36
|
+
flexBasis: 0,
|
|
37
|
+
flexGrow: 2,
|
|
38
|
+
flexShrink: 0,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('supports fractional fr values', () => {
|
|
42
|
+
expect(getColumnStyle('0.5fr', 120)).toEqual({
|
|
43
|
+
minWidth: 120,
|
|
44
|
+
flexBasis: 0,
|
|
45
|
+
flexGrow: 0.5,
|
|
46
|
+
flexShrink: 0,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('uses flex weights from object column widths', () => {
|
|
50
|
+
expect(getColumnStyle({ flex: 3 }, 120)).toEqual({
|
|
51
|
+
minWidth: 120,
|
|
52
|
+
flexBasis: 0,
|
|
53
|
+
flexGrow: 3,
|
|
54
|
+
flexShrink: 0,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
it('falls back to a single flexible column for invalid flexible values', () => {
|
|
58
|
+
expect(getColumnStyle('invalid', 120)).toEqual({
|
|
59
|
+
minWidth: 120,
|
|
60
|
+
flexBasis: 0,
|
|
61
|
+
flexGrow: 1,
|
|
62
|
+
flexShrink: 0,
|
|
63
|
+
});
|
|
64
|
+
expect(getColumnStyle({ flex: 0 }, 120)).toEqual({
|
|
65
|
+
minWidth: 120,
|
|
66
|
+
flexBasis: 0,
|
|
67
|
+
flexGrow: 1,
|
|
68
|
+
flexShrink: 0,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Children, cloneElement, isValidElement } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
const TableBody = ({ children, ...props }) => {
|
|
5
|
+
const items = Children.toArray(children);
|
|
6
|
+
return (_jsx(View, { ...props, children: items.map((child, index) => {
|
|
7
|
+
if (!isValidElement(child)) {
|
|
8
|
+
return child;
|
|
9
|
+
}
|
|
10
|
+
return cloneElement(child, {
|
|
11
|
+
isLastRow: index === items.length - 1,
|
|
12
|
+
});
|
|
13
|
+
}) }));
|
|
14
|
+
};
|
|
15
|
+
TableBody.displayName = 'TableBody';
|
|
16
|
+
export default TableBody;
|