@idealyst/components 1.0.82 → 1.0.84
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/CLAUDE.md +199 -232
- package/README.md +5 -5
- package/package.json +25 -7
- package/plugin/README.md +272 -0
- package/plugin/test-cases.jsx +112 -0
- package/plugin/web-legacy.js +320 -0
- package/plugin/web.js +422 -124
- package/src/Accordion/Accordion.native.tsx +182 -0
- package/src/Accordion/Accordion.styles.tsx +260 -0
- package/src/Accordion/Accordion.web.tsx +147 -0
- package/src/Accordion/index.native.tsx +3 -0
- package/src/Accordion/index.ts +3 -0
- package/src/Accordion/index.web.tsx +3 -0
- package/src/Accordion/types.ts +23 -0
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +17 -12
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +83 -109
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +23 -17
- package/src/ActivityIndicator/index.ts +5 -2
- package/src/ActivityIndicator/index.web.ts +5 -2
- package/src/ActivityIndicator/types.ts +15 -10
- package/src/Alert/Alert.native.tsx +113 -0
- package/src/Alert/Alert.styles.tsx +304 -0
- package/src/Alert/Alert.web.tsx +123 -0
- package/src/Alert/index.native.ts +5 -0
- package/src/Alert/index.ts +5 -0
- package/src/Alert/index.web.ts +5 -0
- package/src/Alert/types.ts +21 -0
- package/src/Avatar/Avatar.native.tsx +8 -6
- package/src/Avatar/Avatar.styles.tsx +64 -58
- package/src/Avatar/Avatar.web.tsx +13 -8
- package/src/Avatar/index.ts +5 -2
- package/src/Avatar/index.web.ts +5 -2
- package/src/Avatar/types.ts +19 -13
- package/src/Badge/Badge.native.tsx +59 -14
- package/src/Badge/Badge.styles.tsx +125 -139
- package/src/Badge/Badge.web.tsx +72 -16
- package/src/Badge/index.ts +5 -2
- package/src/Badge/index.web.ts +5 -2
- package/src/Badge/types.ts +23 -11
- package/src/Breadcrumb/Breadcrumb.native.tsx +225 -0
- package/src/Breadcrumb/Breadcrumb.styles.tsx +234 -0
- package/src/Breadcrumb/Breadcrumb.web.tsx +268 -0
- package/src/Breadcrumb/index.native.ts +5 -0
- package/src/Breadcrumb/index.ts +5 -0
- package/src/Breadcrumb/index.web.ts +5 -0
- package/src/Breadcrumb/types.ts +56 -0
- package/src/Button/Button.native.tsx +75 -24
- package/src/Button/Button.styles.tsx +248 -205
- package/src/Button/Button.web.tsx +82 -25
- package/src/Button/index.ts +5 -5
- package/src/Button/index.web.ts +5 -3
- package/src/Button/types.ts +32 -15
- package/src/Card/Card.native.tsx +14 -11
- package/src/Card/Card.styles.tsx +146 -220
- package/src/Card/Card.web.tsx +20 -21
- package/src/Card/index.ts +5 -5
- package/src/Card/index.web.ts +5 -3
- package/src/Card/types.ts +24 -17
- package/src/Checkbox/Checkbox.native.tsx +24 -34
- package/src/Checkbox/Checkbox.styles.tsx +223 -275
- package/src/Checkbox/Checkbox.web.tsx +30 -37
- package/src/Checkbox/index.ts +5 -5
- package/src/Checkbox/index.web.ts +5 -3
- package/src/Checkbox/types.ts +26 -20
- package/src/Chip/Chip.native.tsx +126 -0
- package/src/Chip/Chip.styles.tsx +138 -0
- package/src/Chip/Chip.web.tsx +154 -0
- package/src/Chip/index.native.ts +5 -0
- package/src/Chip/index.ts +5 -0
- package/src/Chip/index.web.ts +5 -0
- package/src/Chip/types.ts +51 -0
- package/src/Dialog/Dialog.native.tsx +65 -12
- package/src/Dialog/Dialog.styles.tsx +154 -136
- package/src/Dialog/Dialog.web.tsx +16 -11
- package/src/Dialog/index.ts +5 -2
- package/src/Dialog/index.web.ts +5 -2
- package/src/Dialog/types.ts +22 -16
- package/src/Divider/Divider.native.tsx +19 -14
- package/src/Divider/Divider.styles.tsx +273 -595
- package/src/Divider/Divider.web.tsx +19 -12
- package/src/Divider/index.ts +5 -5
- package/src/Divider/index.web.ts +5 -3
- package/src/Divider/types.ts +28 -19
- package/src/Icon/Icon.native.tsx +17 -24
- package/src/Icon/Icon.styles.tsx +64 -48
- package/src/Icon/Icon.web.tsx +14 -11
- package/src/Icon/IconSvg/IconSvg.native.tsx +42 -0
- package/src/Icon/IconSvg/IconSvg.web.tsx +40 -0
- package/src/Icon/IconSvg/index.native.ts +1 -0
- package/src/Icon/IconSvg/index.ts +1 -0
- package/src/Icon/icon-resolver.native.ts +27 -0
- package/src/Icon/icon-resolver.ts +70 -0
- package/src/Icon/index.ts +5 -5
- package/src/Icon/index.web.ts +5 -3
- package/src/Icon/types.ts +17 -11
- package/src/Image/Image.native.tsx +86 -0
- package/src/Image/Image.styles.tsx +57 -0
- package/src/Image/Image.web.tsx +92 -0
- package/src/Image/index.native.ts +5 -0
- package/src/Image/index.ts +5 -0
- package/src/Image/types.ts +21 -0
- package/src/Input/Input.native.tsx +103 -26
- package/src/Input/Input.styles.tsx +240 -177
- package/src/Input/Input.web.tsx +141 -38
- package/src/Input/index.ts +5 -5
- package/src/Input/index.web.ts +5 -3
- package/src/Input/types.ts +43 -20
- package/src/List/List.native.tsx +56 -0
- package/src/List/List.styles.tsx +257 -0
- package/src/List/List.web.tsx +43 -0
- package/src/List/ListContext.tsx +16 -0
- package/src/List/ListItem.native.tsx +111 -0
- package/src/List/ListItem.web.tsx +110 -0
- package/src/List/ListSection.native.tsx +31 -0
- package/src/List/ListSection.web.tsx +33 -0
- package/src/List/index.native.tsx +5 -0
- package/src/List/index.ts +5 -0
- package/src/List/index.web.tsx +5 -0
- package/src/List/types.ts +42 -0
- package/src/Menu/Menu.native.tsx +150 -0
- package/src/Menu/Menu.styles.tsx +185 -0
- package/src/Menu/Menu.web.tsx +99 -0
- package/src/Menu/MenuItem.native.tsx +66 -0
- package/src/Menu/MenuItem.styles.tsx +119 -0
- package/src/Menu/MenuItem.web.tsx +67 -0
- package/src/Menu/index.native.ts +3 -0
- package/src/Menu/index.ts +3 -0
- package/src/Menu/index.web.ts +3 -0
- package/src/Menu/types.ts +30 -0
- package/src/Popover/Popover.native.tsx +102 -32
- package/src/Popover/Popover.styles.tsx +100 -67
- package/src/Popover/Popover.web.tsx +36 -260
- package/src/Popover/index.ts +5 -2
- package/src/Popover/index.web.ts +5 -2
- package/src/Popover/types.ts +14 -13
- package/src/Pressable/Pressable.native.tsx +7 -6
- package/src/Pressable/Pressable.web.tsx +8 -6
- package/src/Pressable/index.ts +5 -2
- package/src/Pressable/index.web.ts +5 -2
- package/src/Pressable/types.ts +11 -10
- package/src/Progress/Progress.native.tsx +179 -0
- package/src/Progress/Progress.styles.tsx +164 -0
- package/src/Progress/Progress.web.tsx +144 -0
- package/src/Progress/index.native.ts +1 -0
- package/src/Progress/index.ts +5 -0
- package/src/Progress/index.web.ts +5 -0
- package/src/Progress/types.ts +21 -0
- package/src/RadioButton/RadioButton.native.tsx +88 -0
- package/src/RadioButton/RadioButton.styles.tsx +163 -0
- package/src/RadioButton/RadioButton.web.tsx +85 -0
- package/src/RadioButton/RadioGroup.native.tsx +43 -0
- package/src/RadioButton/RadioGroup.web.tsx +49 -0
- package/src/RadioButton/index.native.ts +2 -0
- package/src/RadioButton/index.ts +2 -0
- package/src/RadioButton/index.web.ts +2 -0
- package/src/RadioButton/types.ts +29 -0
- package/src/SVGImage/SVGImage.native.tsx +9 -7
- package/src/SVGImage/SVGImage.styles.tsx +63 -55
- package/src/SVGImage/SVGImage.web.tsx +16 -13
- package/src/SVGImage/index.ts +5 -5
- package/src/SVGImage/index.web.ts +5 -2
- package/src/SVGImage/types.ts +7 -3
- package/src/Screen/Screen.native.tsx +43 -17
- package/src/Screen/Screen.styles.tsx +58 -54
- package/src/Screen/Screen.web.tsx +11 -5
- package/src/Screen/index.ts +5 -2
- package/src/Screen/index.web.ts +5 -2
- package/src/Screen/types.ts +23 -9
- package/src/Select/Select.native.tsx +347 -0
- package/src/Select/Select.styles.tsx +335 -0
- package/src/Select/Select.web.tsx +276 -0
- package/src/Select/index.native.ts +2 -0
- package/src/Select/index.ts +5 -0
- package/src/Select/index.web.ts +5 -0
- package/src/Select/types.ts +124 -0
- package/src/Skeleton/Skeleton.native.tsx +139 -0
- package/src/Skeleton/Skeleton.styles.tsx +59 -0
- package/src/Skeleton/Skeleton.web.tsx +112 -0
- package/src/Skeleton/index.native.ts +4 -0
- package/src/Skeleton/index.ts +5 -0
- package/src/Skeleton/index.web.ts +5 -0
- package/src/Skeleton/types.ts +75 -0
- package/src/Slider/Slider.native.tsx +248 -0
- package/src/Slider/Slider.styles.tsx +241 -0
- package/src/Slider/Slider.web.tsx +226 -0
- package/src/Slider/index.native.ts +3 -0
- package/src/Slider/index.ts +5 -0
- package/src/Slider/index.web.ts +5 -0
- package/src/Slider/types.ts +31 -0
- package/src/Switch/Switch.native.tsx +131 -0
- package/src/Switch/Switch.styles.tsx +169 -0
- package/src/Switch/Switch.web.tsx +121 -0
- package/src/Switch/index.native.ts +3 -0
- package/src/Switch/index.ts +5 -0
- package/src/Switch/index.web.ts +5 -0
- package/src/Switch/types.ts +21 -0
- package/src/TabBar/TabBar.native.tsx +142 -0
- package/src/TabBar/TabBar.styles.tsx +399 -0
- package/src/TabBar/TabBar.web.tsx +205 -0
- package/src/TabBar/index.native.tsx +3 -0
- package/src/TabBar/index.ts +3 -0
- package/src/TabBar/index.web.tsx +3 -0
- package/src/TabBar/types.ts +26 -0
- package/src/Table/Table.native.tsx +122 -0
- package/src/Table/Table.styles.tsx +283 -0
- package/src/Table/Table.web.tsx +112 -0
- package/src/Table/index.native.tsx +3 -0
- package/src/Table/index.ts +3 -0
- package/src/Table/index.web.tsx +3 -0
- package/src/Table/types.ts +28 -0
- package/src/Text/Text.native.tsx +12 -11
- package/src/Text/Text.styles.tsx +76 -64
- package/src/Text/Text.web.tsx +14 -9
- package/src/Text/index.ts +5 -5
- package/src/Text/index.web.ts +5 -3
- package/src/Text/types.ts +20 -13
- package/src/TextArea/TextArea.native.tsx +134 -0
- package/src/TextArea/TextArea.styles.tsx +175 -0
- package/src/TextArea/TextArea.web.tsx +156 -0
- package/src/TextArea/index.native.ts +3 -0
- package/src/TextArea/index.ts +3 -0
- package/src/TextArea/index.web.ts +3 -0
- package/src/TextArea/types.ts +30 -0
- package/src/Tooltip/Tooltip.native.tsx +165 -0
- package/src/Tooltip/Tooltip.styles.tsx +73 -0
- package/src/Tooltip/Tooltip.web.tsx +87 -0
- package/src/Tooltip/index.native.ts +3 -0
- package/src/Tooltip/index.ts +3 -0
- package/src/Tooltip/types.ts +18 -0
- package/src/Video/Video.native.tsx +105 -0
- package/src/Video/Video.styles.tsx +39 -0
- package/src/Video/Video.web.tsx +115 -0
- package/src/Video/index.native.ts +5 -0
- package/src/Video/index.ts +5 -0
- package/src/Video/types.ts +29 -0
- package/src/View/View.native.tsx +9 -14
- package/src/View/View.styles.tsx +101 -93
- package/src/View/View.web.tsx +16 -17
- package/src/View/index.ts +5 -5
- package/src/View/index.web.ts +5 -3
- package/src/View/types.ts +29 -21
- package/src/examples/AccordionExamples.tsx +126 -0
- package/src/examples/AlertExamples.tsx +280 -0
- package/src/examples/AvatarExamples.tsx +23 -23
- package/src/examples/BadgeExamples.tsx +109 -41
- package/src/examples/BreadcrumbExamples.tsx +312 -0
- package/src/examples/ButtonExamples.tsx +160 -33
- package/src/examples/CardExamples.tsx +40 -40
- package/src/examples/CheckboxExamples.tsx +12 -12
- package/src/examples/ChipExamples.tsx +197 -0
- package/src/examples/DialogExamples.tsx +22 -22
- package/src/examples/DividerExamples.tsx +49 -49
- package/src/examples/IconExamples.tsx +270 -54
- package/src/examples/ImageExamples.tsx +174 -0
- package/src/examples/InputExamples.tsx +75 -17
- package/src/examples/ListExamples.tsx +288 -0
- package/src/examples/MenuExamples.tsx +144 -0
- package/src/examples/PopoverExamples.tsx +69 -73
- package/src/examples/ProgressExamples.tsx +137 -0
- package/src/examples/RadioButtonExamples.tsx +161 -0
- package/src/examples/SVGImageExamples.tsx +19 -17
- package/src/examples/ScreenExamples.tsx +31 -31
- package/src/examples/SelectExamples.tsx +423 -0
- package/src/examples/SkeletonExamples.tsx +206 -0
- package/src/examples/SliderExamples.tsx +200 -0
- package/src/examples/SwitchExamples.tsx +182 -0
- package/src/examples/TabBarExamples.tsx +143 -0
- package/src/examples/TableExamples.tsx +280 -0
- package/src/examples/TextAreaExamples.tsx +173 -0
- package/src/examples/TextExamples.tsx +28 -32
- package/src/examples/ThemeExtensionExamples.tsx +10 -10
- package/src/examples/TooltipExamples.tsx +126 -0
- package/src/examples/VideoExamples.tsx +144 -0
- package/src/examples/ViewExamples.tsx +64 -56
- package/src/examples/index.ts +18 -3
- package/src/hooks/useMergeRefs.ts +16 -0
- package/src/hooks/useSmartPosition.native.ts +169 -0
- package/src/index.native.ts +80 -9
- package/src/index.ts +75 -1
- package/src/internal/BoundedModalContent.native.tsx +58 -0
- package/src/internal/PositionedPortal.tsx +254 -0
- package/src/internal/SafeAreaDebugOverlay.native.tsx +173 -0
- package/src/unistyles.d.ts +6 -0
- package/src/utils/buildSizeVariants.ts +16 -0
- package/src/utils/deepMerge.ts +43 -0
- package/src/utils/positionUtils.native.ts +280 -0
- package/src/utils/styleHelpers.ts +48 -0
- package/LLM-ACCESS-GUIDE.md +0 -143
- package/src/ActivityIndicator/README.md +0 -132
- package/src/Avatar/README.md +0 -139
- package/src/Badge/README.md +0 -170
- package/src/Button/Button.types.ts +0 -12
- package/src/Button/README.md +0 -262
- package/src/Card/README.md +0 -258
- package/src/Checkbox/README.md +0 -102
- package/src/Dialog/README.md +0 -210
- package/src/Divider/README.md +0 -108
- package/src/Icon/README.md +0 -81
- package/src/Input/README.md +0 -100
- package/src/SVGImage/README.md +0 -209
- package/src/Screen/README.md +0 -86
- package/src/Text/README.md +0 -94
- package/src/View/README.md +0 -107
- package/src/examples/AllExamples.tsx +0 -84
- package/src/examples/README.md +0 -136
- package/src/examples/ValidationExamples.tsx +0 -95
- package/src/examples/extendedTheme.ts +0 -329
- package/src/theme/breakpoints.ts +0 -8
- package/src/theme/colorResolver.ts +0 -218
- package/src/theme/colors.ts +0 -315
- package/src/theme/defaultThemes.ts +0 -326
- package/src/theme/index.ts +0 -188
- package/src/theme/themeBuilder.ts +0 -602
- package/src/theme/unistyles.d.ts +0 -6
- package/src/theme/variantHelpers.ts +0 -584
- package/src/theme/variants.ts +0 -56
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AllComponentSizes, Size, Theme, Styles } from '@idealyst/theme';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds a generic size variant. Not really useful on its own tbh, just good to show how it can be used.
|
|
5
|
+
* Context really matters for sizes
|
|
6
|
+
* @param theme
|
|
7
|
+
* @param builder
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function buildSizeVariants<T extends keyof AllComponentSizes>(theme: Theme, component: T, builder: (value: AllComponentSizes[T][Size]) => Styles): Record<Size, Styles> {
|
|
11
|
+
const variants = {} as Record<Size, Styles>;
|
|
12
|
+
for (const size in theme.sizes[component]) {
|
|
13
|
+
variants[size as Size] = builder(theme.sizes[component][size as Size]);
|
|
14
|
+
}
|
|
15
|
+
return variants;
|
|
16
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a value is a plain object (not an array, null, or other special object)
|
|
3
|
+
*/
|
|
4
|
+
function isPlainObject(value: unknown): value is Record<string, any> {
|
|
5
|
+
return (
|
|
6
|
+
value !== null &&
|
|
7
|
+
typeof value === 'object' &&
|
|
8
|
+
!Array.isArray(value) &&
|
|
9
|
+
Object.prototype.toString.call(value) === '[object Object]'
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deep merge two objects together, with the second object taking priority.
|
|
15
|
+
* Arrays and non-plain objects are replaced rather than merged.
|
|
16
|
+
*
|
|
17
|
+
* @param target - The base object
|
|
18
|
+
* @param source - The object to merge in (takes priority)
|
|
19
|
+
* @returns A new merged object
|
|
20
|
+
*/
|
|
21
|
+
export function deepMerge<T extends Record<string, any>, S extends Record<string, any>>(
|
|
22
|
+
target: T,
|
|
23
|
+
source: S
|
|
24
|
+
): T & S {
|
|
25
|
+
const result: Record<string, any> = { ...target }
|
|
26
|
+
|
|
27
|
+
for (const key in source) {
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
29
|
+
const sourceValue = source[key]
|
|
30
|
+
const targetValue = result[key]
|
|
31
|
+
|
|
32
|
+
// If both values are plain objects, merge them recursively
|
|
33
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
34
|
+
result[key] = deepMerge(targetValue, sourceValue)
|
|
35
|
+
} else {
|
|
36
|
+
// Otherwise, source value takes priority (including arrays, primitives, null, undefined)
|
|
37
|
+
result[key] = sourceValue
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return result as T & S
|
|
43
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculate the maximum available height for content at a given position
|
|
5
|
+
* This matches the logic in BoundedModalContent to ensure positioning is accurate
|
|
6
|
+
*/
|
|
7
|
+
export const calculateAvailableHeight = (
|
|
8
|
+
top: number,
|
|
9
|
+
safeAreaInsets?: SafeAreaInsets
|
|
10
|
+
): number => {
|
|
11
|
+
const { height: windowHeight } = Dimensions.get('window');
|
|
12
|
+
const padding = 12;
|
|
13
|
+
const bottomSafeEdge = windowHeight - (safeAreaInsets?.bottom || 0);
|
|
14
|
+
const bottomBound = bottomSafeEdge - padding;
|
|
15
|
+
return Math.max(100, bottomBound - top);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type Placement =
|
|
19
|
+
| 'top' | 'top-start' | 'top-end'
|
|
20
|
+
| 'bottom' | 'bottom-start' | 'bottom-end'
|
|
21
|
+
| 'left' | 'left-start' | 'left-end'
|
|
22
|
+
| 'right' | 'right-start' | 'right-end';
|
|
23
|
+
|
|
24
|
+
export interface Position {
|
|
25
|
+
top: number;
|
|
26
|
+
left: number;
|
|
27
|
+
width?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AnchorMeasurements {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ContentSize {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SafeAreaInsets {
|
|
43
|
+
top: number;
|
|
44
|
+
right: number;
|
|
45
|
+
bottom: number;
|
|
46
|
+
left: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the opposite placement (for flipping)
|
|
51
|
+
*/
|
|
52
|
+
const getOppositePlacement = (placement: Placement): Placement => {
|
|
53
|
+
const opposites: Record<Placement, Placement> = {
|
|
54
|
+
'top': 'bottom',
|
|
55
|
+
'top-start': 'bottom-start',
|
|
56
|
+
'top-end': 'bottom-end',
|
|
57
|
+
'bottom': 'top',
|
|
58
|
+
'bottom-start': 'top-start',
|
|
59
|
+
'bottom-end': 'top-end',
|
|
60
|
+
'left': 'right',
|
|
61
|
+
'left-start': 'right-start',
|
|
62
|
+
'left-end': 'right-end',
|
|
63
|
+
'right': 'left',
|
|
64
|
+
'right-start': 'left-start',
|
|
65
|
+
'right-end': 'left-end',
|
|
66
|
+
};
|
|
67
|
+
return opposites[placement];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Calculate position for a given placement without boundary checks
|
|
72
|
+
*/
|
|
73
|
+
const calculatePositionForPlacement = (
|
|
74
|
+
anchor: AnchorMeasurements,
|
|
75
|
+
contentSize: ContentSize,
|
|
76
|
+
placement: Placement,
|
|
77
|
+
offset: number
|
|
78
|
+
): Position => {
|
|
79
|
+
let top = 0;
|
|
80
|
+
let left = 0;
|
|
81
|
+
|
|
82
|
+
switch (placement) {
|
|
83
|
+
case 'top':
|
|
84
|
+
top = anchor.y - contentSize.height - offset;
|
|
85
|
+
left = anchor.x + anchor.width / 2 - contentSize.width / 2;
|
|
86
|
+
break;
|
|
87
|
+
case 'top-start':
|
|
88
|
+
top = anchor.y - contentSize.height - offset;
|
|
89
|
+
left = anchor.x;
|
|
90
|
+
break;
|
|
91
|
+
case 'top-end':
|
|
92
|
+
top = anchor.y - contentSize.height - offset;
|
|
93
|
+
left = anchor.x + anchor.width - contentSize.width;
|
|
94
|
+
break;
|
|
95
|
+
case 'bottom':
|
|
96
|
+
top = anchor.y + anchor.height + offset;
|
|
97
|
+
left = anchor.x + anchor.width / 2 - contentSize.width / 2;
|
|
98
|
+
break;
|
|
99
|
+
case 'bottom-start':
|
|
100
|
+
top = anchor.y + anchor.height + offset;
|
|
101
|
+
left = anchor.x;
|
|
102
|
+
break;
|
|
103
|
+
case 'bottom-end':
|
|
104
|
+
top = anchor.y + anchor.height + offset;
|
|
105
|
+
left = anchor.x + anchor.width - contentSize.width;
|
|
106
|
+
break;
|
|
107
|
+
case 'left':
|
|
108
|
+
top = anchor.y + anchor.height / 2 - contentSize.height / 2;
|
|
109
|
+
left = anchor.x - contentSize.width - offset;
|
|
110
|
+
break;
|
|
111
|
+
case 'left-start':
|
|
112
|
+
top = anchor.y;
|
|
113
|
+
left = anchor.x - contentSize.width - offset;
|
|
114
|
+
break;
|
|
115
|
+
case 'left-end':
|
|
116
|
+
top = anchor.y + anchor.height - contentSize.height;
|
|
117
|
+
left = anchor.x - contentSize.width - offset;
|
|
118
|
+
break;
|
|
119
|
+
case 'right':
|
|
120
|
+
top = anchor.y + anchor.height / 2 - contentSize.height / 2;
|
|
121
|
+
left = anchor.x + anchor.width + offset;
|
|
122
|
+
break;
|
|
123
|
+
case 'right-start':
|
|
124
|
+
top = anchor.y;
|
|
125
|
+
left = anchor.x + anchor.width + offset;
|
|
126
|
+
break;
|
|
127
|
+
case 'right-end':
|
|
128
|
+
top = anchor.y + anchor.height - contentSize.height;
|
|
129
|
+
left = anchor.x + anchor.width + offset;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { top, left };
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if position fits within window bounds accounting for safe areas
|
|
138
|
+
* Position is in window coordinates (from measureInWindow)
|
|
139
|
+
*/
|
|
140
|
+
const fitsInViewport = (
|
|
141
|
+
position: Position,
|
|
142
|
+
contentSize: ContentSize,
|
|
143
|
+
windowSize: { width: number; height: number },
|
|
144
|
+
padding: number = 12,
|
|
145
|
+
safeAreaInsets?: SafeAreaInsets
|
|
146
|
+
): boolean => {
|
|
147
|
+
const right = position.left + contentSize.width;
|
|
148
|
+
const bottom = position.top + contentSize.height;
|
|
149
|
+
|
|
150
|
+
// Calculate bounds in window coordinates
|
|
151
|
+
// Respect safe areas to avoid overlapping with system UI
|
|
152
|
+
const topBound = padding + (safeAreaInsets?.top || 0);
|
|
153
|
+
const leftBound = padding + (safeAreaInsets?.left || 0);
|
|
154
|
+
const rightBound = windowSize.width - padding - (safeAreaInsets?.right || 0);
|
|
155
|
+
const bottomBound = windowSize.height - topBound - (safeAreaInsets?.bottom || 0) - padding;
|
|
156
|
+
|
|
157
|
+
// Add a buffer to account for floating point precision and give some breathing room
|
|
158
|
+
const buffer = 2;
|
|
159
|
+
|
|
160
|
+
const fits = (
|
|
161
|
+
position.left >= leftBound &&
|
|
162
|
+
position.top >= topBound &&
|
|
163
|
+
right <= rightBound - buffer &&
|
|
164
|
+
bottom <= bottomBound - buffer
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
console.log(bottom, bottomBound);
|
|
168
|
+
|
|
169
|
+
if (__DEV__) {
|
|
170
|
+
console.log('[fitsInViewport]', {
|
|
171
|
+
position,
|
|
172
|
+
contentSize,
|
|
173
|
+
windowSize,
|
|
174
|
+
safeAreaInsets,
|
|
175
|
+
bounds: { topBound, leftBound, rightBound, bottomBound },
|
|
176
|
+
edges: { right, bottom },
|
|
177
|
+
fits,
|
|
178
|
+
bottomOverflow: bottom > bottomBound ? bottom - bottomBound : 0
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return fits;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Calculate the best position with smart boundary detection and flipping
|
|
187
|
+
*/
|
|
188
|
+
export const calculateSmartPosition = (
|
|
189
|
+
anchor: AnchorMeasurements,
|
|
190
|
+
contentSize: ContentSize,
|
|
191
|
+
placement: Placement,
|
|
192
|
+
offset: number = 8,
|
|
193
|
+
matchWidth: boolean = false,
|
|
194
|
+
safeAreaInsets?: SafeAreaInsets
|
|
195
|
+
): Position => {
|
|
196
|
+
// Use window dimensions - this is the actual visible content area
|
|
197
|
+
const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
|
|
198
|
+
const padding = 12;
|
|
199
|
+
|
|
200
|
+
// Calculate actual usable space accounting for safe areas
|
|
201
|
+
const topBound = padding;
|
|
202
|
+
const rightBound = windowWidth - padding - (safeAreaInsets?.right || 0);
|
|
203
|
+
const bottomBound = windowHeight - padding - (safeAreaInsets?.bottom || 0);
|
|
204
|
+
const leftBound = padding + (safeAreaInsets?.left || 0);
|
|
205
|
+
|
|
206
|
+
// Try original placement
|
|
207
|
+
let position = calculatePositionForPlacement(anchor, contentSize, placement, offset);
|
|
208
|
+
|
|
209
|
+
// Check if it fits using window dimensions
|
|
210
|
+
const windowSize = { width: windowWidth, height: windowHeight };
|
|
211
|
+
const originalFits = fitsInViewport(position, contentSize, windowSize, padding, safeAreaInsets);
|
|
212
|
+
|
|
213
|
+
if (__DEV__) {
|
|
214
|
+
console.log('[calculateSmartPosition] Original placement:', placement, 'fits:', originalFits, 'position:', position);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (originalFits) {
|
|
218
|
+
if (matchWidth) {
|
|
219
|
+
position.width = anchor.width;
|
|
220
|
+
}
|
|
221
|
+
return position;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Try flipping to opposite side
|
|
225
|
+
const oppositePlacement = getOppositePlacement(placement);
|
|
226
|
+
let flippedPosition = calculatePositionForPlacement(anchor, contentSize, oppositePlacement, offset);
|
|
227
|
+
const flippedFits = fitsInViewport(flippedPosition, contentSize, windowSize, padding, safeAreaInsets);
|
|
228
|
+
|
|
229
|
+
if (__DEV__) {
|
|
230
|
+
console.log('[calculateSmartPosition] Flipped placement:', oppositePlacement, 'fits:', flippedFits, 'position:', flippedPosition);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (flippedFits) {
|
|
234
|
+
if (matchWidth) {
|
|
235
|
+
flippedPosition.width = anchor.width;
|
|
236
|
+
}
|
|
237
|
+
return flippedPosition;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Try alternative alignments for the original side
|
|
241
|
+
const alternativePlacements: Placement[] = [];
|
|
242
|
+
const basePlacement = placement.split('-')[0] as 'top' | 'bottom' | 'left' | 'right';
|
|
243
|
+
|
|
244
|
+
if (placement.includes('-')) {
|
|
245
|
+
// If we have an alignment, try other alignments on the same side
|
|
246
|
+
alternativePlacements.push(`${basePlacement}` as Placement);
|
|
247
|
+
if (!placement.endsWith('-start')) alternativePlacements.push(`${basePlacement}-start` as Placement);
|
|
248
|
+
if (!placement.endsWith('-end')) alternativePlacements.push(`${basePlacement}-end` as Placement);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Try alternative alignments
|
|
252
|
+
for (const altPlacement of alternativePlacements) {
|
|
253
|
+
const altPosition = calculatePositionForPlacement(anchor, contentSize, altPlacement, offset);
|
|
254
|
+
if (fitsInViewport(altPosition, contentSize, windowSize, padding, safeAreaInsets)) {
|
|
255
|
+
if (matchWidth) {
|
|
256
|
+
altPosition.width = anchor.width;
|
|
257
|
+
}
|
|
258
|
+
return altPosition;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// If nothing fits perfectly, constrain to viewport bounds as fallback
|
|
263
|
+
// This handles when content is too large to fit anywhere
|
|
264
|
+
if (__DEV__) {
|
|
265
|
+
console.log('[calculateSmartPosition] Nothing fits, constraining to bounds. Original position:', position, 'bounds:', { topBound, bottomBound, leftBound, rightBound });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
position.left = Math.max(leftBound, Math.min(position.left, rightBound - contentSize.width));
|
|
269
|
+
position.top = Math.max(topBound, Math.min(position.top, bottomBound - contentSize.height));
|
|
270
|
+
|
|
271
|
+
if (__DEV__) {
|
|
272
|
+
console.log('[calculateSmartPosition] Constrained position:', position);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (matchWidth) {
|
|
276
|
+
position.width = anchor.width;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return position;
|
|
280
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Theme } from '@idealyst/theme';
|
|
2
|
+
|
|
3
|
+
// Type definitions
|
|
4
|
+
export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
|
|
6
|
+
// Helper functions
|
|
7
|
+
export function isPlainObject(value: unknown): value is Record<string, any> {
|
|
8
|
+
return (
|
|
9
|
+
value !== null &&
|
|
10
|
+
typeof value === 'object' &&
|
|
11
|
+
!Array.isArray(value) &&
|
|
12
|
+
Object.prototype.toString.call(value) === '[object Object]'
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function deepMerge<T extends Record<string, any>, S extends Record<string, any>>(
|
|
17
|
+
target: T,
|
|
18
|
+
source: S
|
|
19
|
+
): T & S {
|
|
20
|
+
const result: Record<string, any> = { ...target }
|
|
21
|
+
|
|
22
|
+
for (const key in source) {
|
|
23
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
24
|
+
const sourceValue = source[key]
|
|
25
|
+
const targetValue = result[key]
|
|
26
|
+
|
|
27
|
+
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
|
|
28
|
+
result[key] = deepMerge(targetValue, sourceValue)
|
|
29
|
+
} else {
|
|
30
|
+
result[key] = sourceValue
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result as T & S
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildSizeVariants<T>(
|
|
39
|
+
theme: Theme,
|
|
40
|
+
component: string,
|
|
41
|
+
builder: (value: any) => any
|
|
42
|
+
): Record<Size, any> {
|
|
43
|
+
const variants = {} as Record<Size, any>;
|
|
44
|
+
for (const size in (theme.sizes as any)[component]) {
|
|
45
|
+
variants[size as Size] = builder((theme.sizes as any)[component][size as Size]);
|
|
46
|
+
}
|
|
47
|
+
return variants;
|
|
48
|
+
}
|
package/LLM-ACCESS-GUIDE.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# LLM Documentation Access Guide
|
|
2
|
-
|
|
3
|
-
This guide explains exactly how LLMs can access @idealyst/components documentation in real-world scenarios.
|
|
4
|
-
|
|
5
|
-
## Scenario 1: Working in a Project with the Package Installed
|
|
6
|
-
|
|
7
|
-
When helping a developer who has `@idealyst/components` installed in their project:
|
|
8
|
-
|
|
9
|
-
### Quick Overview
|
|
10
|
-
```bash
|
|
11
|
-
# Read the main documentation file
|
|
12
|
-
cat node_modules/@idealyst/components/CLAUDE.md
|
|
13
|
-
```
|
|
14
|
-
This gives you everything in one file - all components, patterns, and usage examples.
|
|
15
|
-
|
|
16
|
-
### Specific Component Details
|
|
17
|
-
```bash
|
|
18
|
-
# For detailed Button documentation
|
|
19
|
-
cat node_modules/@idealyst/components/src/Button/README.md
|
|
20
|
-
|
|
21
|
-
# For detailed Card documentation
|
|
22
|
-
cat node_modules/@idealyst/components/src/Card/README.md
|
|
23
|
-
|
|
24
|
-
# And so on for each component...
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Component Discovery
|
|
28
|
-
```bash
|
|
29
|
-
# See all available components
|
|
30
|
-
ls node_modules/@idealyst/components/src/*/README.md
|
|
31
|
-
|
|
32
|
-
# Will show:
|
|
33
|
-
# node_modules/@idealyst/components/src/Avatar/README.md
|
|
34
|
-
# node_modules/@idealyst/components/src/Badge/README.md
|
|
35
|
-
# node_modules/@idealyst/components/src/Button/README.md
|
|
36
|
-
# ... etc
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Scenario 2: Repository/GitHub Access
|
|
40
|
-
|
|
41
|
-
When working with the source repository or GitHub:
|
|
42
|
-
|
|
43
|
-
### Main Documentation
|
|
44
|
-
- `packages/components/README.md` - Complete overview with component table
|
|
45
|
-
- `packages/components/CLAUDE.md` - LLM-optimized quick reference
|
|
46
|
-
|
|
47
|
-
### Individual Component Docs
|
|
48
|
-
- `packages/components/src/Avatar/README.md`
|
|
49
|
-
- `packages/components/src/Button/README.md`
|
|
50
|
-
- `packages/components/src/Card/README.md`
|
|
51
|
-
- ... etc for all 11 components
|
|
52
|
-
|
|
53
|
-
## Scenario 3: Package Manager Info
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# View package information
|
|
57
|
-
npm info @idealyst/components
|
|
58
|
-
|
|
59
|
-
# View package README
|
|
60
|
-
npm docs @idealyst/components
|
|
61
|
-
|
|
62
|
-
# Download and examine (if needed)
|
|
63
|
-
npm pack @idealyst/components
|
|
64
|
-
tar -tf idealyst-components-*.tgz | grep README
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Scenario 4: Examples and Live Code
|
|
68
|
-
|
|
69
|
-
For working examples and demonstrations:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
# Import component examples
|
|
73
|
-
import { ButtonExamples, CardExamples } from '@idealyst/components/examples';
|
|
74
|
-
|
|
75
|
-
# Or read example source code
|
|
76
|
-
cat node_modules/@idealyst/components/src/examples/ButtonExamples.tsx
|
|
77
|
-
cat node_modules/@idealyst/components/src/examples/CardExamples.tsx
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Recommended LLM Workflow
|
|
81
|
-
|
|
82
|
-
### Step 1: Quick Reference
|
|
83
|
-
Start with `CLAUDE.md` for:
|
|
84
|
-
- All component names and categories
|
|
85
|
-
- Common usage patterns
|
|
86
|
-
- Intent system explanation
|
|
87
|
-
- Quick examples
|
|
88
|
-
|
|
89
|
-
### Step 2: Specific Component Help
|
|
90
|
-
When user asks about a specific component:
|
|
91
|
-
1. Read `src/ComponentName/README.md` for complete details
|
|
92
|
-
2. Use the detailed props table, examples, and best practices
|
|
93
|
-
|
|
94
|
-
### Step 3: Component Discovery
|
|
95
|
-
When user asks "what components are available for X":
|
|
96
|
-
1. Check the component table in main README.md
|
|
97
|
-
2. Use component categories (layout, form, display, etc.)
|
|
98
|
-
3. Read individual component descriptions
|
|
99
|
-
|
|
100
|
-
## File Locations Summary
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
@idealyst/components/
|
|
104
|
-
├── README.md # Complete overview + component table
|
|
105
|
-
├── CLAUDE.md # LLM quick reference (START HERE)
|
|
106
|
-
├── src/
|
|
107
|
-
│ ├── Avatar/README.md # Detailed Avatar docs
|
|
108
|
-
│ ├── Badge/README.md # Detailed Badge docs
|
|
109
|
-
│ ├── Button/README.md # Detailed Button docs
|
|
110
|
-
│ ├── Card/README.md # Detailed Card docs
|
|
111
|
-
│ ├── Checkbox/README.md # Detailed Checkbox docs
|
|
112
|
-
│ ├── Divider/README.md # Detailed Divider docs
|
|
113
|
-
│ ├── Icon/README.md # Detailed Icon docs
|
|
114
|
-
│ ├── Input/README.md # Detailed Input docs
|
|
115
|
-
│ ├── Screen/README.md # Detailed Screen docs
|
|
116
|
-
│ ├── Text/README.md # Detailed Text docs
|
|
117
|
-
│ └── View/README.md # Detailed View docs
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Pro Tips for LLMs
|
|
121
|
-
|
|
122
|
-
1. **Always start with `CLAUDE.md`** - it has everything you need for 90% of questions
|
|
123
|
-
2. **Component table in main README** - perfect for "what components are available" questions
|
|
124
|
-
3. **Individual README files** - use when user needs specific component details
|
|
125
|
-
4. **Check the examples/** folder - contains working code examples for all components
|
|
126
|
-
5. **Intent system is key** - primary, neutral, success, error, warning are used across all components
|
|
127
|
-
|
|
128
|
-
## Quick Command Reference
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# Essential reads for any LLM session
|
|
132
|
-
cat CLAUDE.md # Complete LLM reference
|
|
133
|
-
cat README.md # Overview + component table
|
|
134
|
-
|
|
135
|
-
# Specific component help
|
|
136
|
-
cat src/Button/README.md # Button documentation
|
|
137
|
-
cat src/Card/README.md # Card documentation
|
|
138
|
-
|
|
139
|
-
# Discovery
|
|
140
|
-
ls src/*/README.md # List all component docs
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
This approach ensures LLMs have clear, practical access to all documentation without cluttering the main component API.
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# ActivityIndicator
|
|
2
|
-
|
|
3
|
-
A cross-platform loading indicator component that displays a spinning animation to indicate loading or processing state.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Cross-platform**: Works seamlessly on both React and React Native
|
|
8
|
-
- **Intent-based colors**: Uses semantic color system (primary, neutral, success, error, warning)
|
|
9
|
-
- **Multiple sizes**: Supports small, medium, large, or custom numeric sizes
|
|
10
|
-
- **Customizable**: Override colors and styles as needed
|
|
11
|
-
- **Animation control**: Start/stop animation with `animating` prop
|
|
12
|
-
- **Auto-hide**: Optionally hide when not animating
|
|
13
|
-
|
|
14
|
-
## Usage
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
import { ActivityIndicator } from '@idealyst/components';
|
|
18
|
-
|
|
19
|
-
// Basic usage
|
|
20
|
-
<ActivityIndicator />
|
|
21
|
-
|
|
22
|
-
// With different sizes
|
|
23
|
-
<ActivityIndicator size="small" />
|
|
24
|
-
<ActivityIndicator size="medium" />
|
|
25
|
-
<ActivityIndicator size="large" />
|
|
26
|
-
<ActivityIndicator size={64} /> // Custom size in pixels
|
|
27
|
-
|
|
28
|
-
// With different intents
|
|
29
|
-
<ActivityIndicator intent="primary" />
|
|
30
|
-
<ActivityIndicator intent="success" />
|
|
31
|
-
<ActivityIndicator intent="error" />
|
|
32
|
-
<ActivityIndicator intent="warning" />
|
|
33
|
-
<ActivityIndicator intent="neutral" />
|
|
34
|
-
|
|
35
|
-
// Custom color
|
|
36
|
-
<ActivityIndicator color="#FF5733" />
|
|
37
|
-
|
|
38
|
-
// Control animation
|
|
39
|
-
<ActivityIndicator animating={isLoading} />
|
|
40
|
-
|
|
41
|
-
// Don't hide when stopped
|
|
42
|
-
<ActivityIndicator animating={false} hidesWhenStopped={false} />
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Props
|
|
46
|
-
|
|
47
|
-
| Prop | Type | Default | Description |
|
|
48
|
-
|------|------|---------|-------------|
|
|
49
|
-
| `animating` | `boolean` | `true` | Whether the indicator is animating (visible) |
|
|
50
|
-
| `size` | `'small' \| 'medium' \| 'large' \| number` | `'medium'` | The size of the indicator |
|
|
51
|
-
| `intent` | `'primary' \| 'neutral' \| 'success' \| 'error' \| 'warning'` | `'primary'` | The color intent of the indicator |
|
|
52
|
-
| `color` | `string` | - | Custom color to override intent |
|
|
53
|
-
| `style` | `ViewStyle` | - | Additional styles to apply to the container |
|
|
54
|
-
| `testID` | `string` | - | Test identifier for testing |
|
|
55
|
-
| `hidesWhenStopped` | `boolean` | `true` | Whether to hide the indicator when not animating |
|
|
56
|
-
|
|
57
|
-
## Examples
|
|
58
|
-
|
|
59
|
-
### Loading State
|
|
60
|
-
```tsx
|
|
61
|
-
const LoadingScreen = () => {
|
|
62
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
66
|
-
<ActivityIndicator
|
|
67
|
-
animating={isLoading}
|
|
68
|
-
size="large"
|
|
69
|
-
intent="primary"
|
|
70
|
-
/>
|
|
71
|
-
{isLoading && <Text>Loading data...</Text>}
|
|
72
|
-
</View>
|
|
73
|
-
);
|
|
74
|
-
};
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Button with Loading
|
|
78
|
-
```tsx
|
|
79
|
-
const SubmitButton = ({ onSubmit }) => {
|
|
80
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
81
|
-
|
|
82
|
-
const handleSubmit = async () => {
|
|
83
|
-
setIsSubmitting(true);
|
|
84
|
-
await onSubmit();
|
|
85
|
-
setIsSubmitting(false);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<Button onPress={handleSubmit} disabled={isSubmitting}>
|
|
90
|
-
{isSubmitting ? (
|
|
91
|
-
<ActivityIndicator size="small" color="white" />
|
|
92
|
-
) : (
|
|
93
|
-
'Submit'
|
|
94
|
-
)}
|
|
95
|
-
</Button>
|
|
96
|
-
);
|
|
97
|
-
};
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Custom Styled Indicator
|
|
101
|
-
```tsx
|
|
102
|
-
<ActivityIndicator
|
|
103
|
-
size={50}
|
|
104
|
-
color="#8B5CF6"
|
|
105
|
-
style={{
|
|
106
|
-
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
|
107
|
-
padding: 20,
|
|
108
|
-
borderRadius: 10,
|
|
109
|
-
}}
|
|
110
|
-
/>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Platform Differences
|
|
114
|
-
|
|
115
|
-
- **Web**: Uses CSS animations with a custom spinner implementation
|
|
116
|
-
- **Native**: Uses React Native's built-in `ActivityIndicator` component
|
|
117
|
-
- Both platforms support all the same props for consistency
|
|
118
|
-
|
|
119
|
-
## Accessibility
|
|
120
|
-
|
|
121
|
-
The ActivityIndicator component includes:
|
|
122
|
-
- Proper ARIA roles for screen readers on web
|
|
123
|
-
- Visual indication of loading state
|
|
124
|
-
- Support for test IDs for testing
|
|
125
|
-
|
|
126
|
-
## Best Practices
|
|
127
|
-
|
|
128
|
-
1. **Always provide context**: Pair with text to explain what's loading
|
|
129
|
-
2. **Use appropriate sizes**: Small for inline, large for full-screen loading
|
|
130
|
-
3. **Match intent to context**: Use error intent for retry states, success for completion
|
|
131
|
-
4. **Consider animation performance**: Avoid too many simultaneous indicators
|
|
132
|
-
5. **Provide alternative content**: Show skeleton screens or placeholders when appropriate
|