@idealyst/components 1.2.40 → 1.2.42

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.40",
3
+ "version": "1.2.42",
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.40",
59
+ "@idealyst/theme": "^1.2.42",
60
60
  "@mdi/js": ">=7.0.0",
61
61
  "@mdi/react": ">=1.0.0",
62
62
  "@react-native-vector-icons/common": ">=12.0.0",
@@ -106,7 +106,8 @@
106
106
  }
107
107
  },
108
108
  "devDependencies": {
109
- "@idealyst/theme": "^1.2.40",
109
+ "@idealyst/blur": "^1.2.40",
110
+ "@idealyst/theme": "^1.2.42",
110
111
  "@idealyst/tooling": "^1.2.30",
111
112
  "@mdi/react": "^1.6.1",
112
113
  "@types/react": "^19.1.0",
@@ -18,6 +18,7 @@ const Dialog = forwardRef<View, DialogProps>(({
18
18
  style,
19
19
  testID,
20
20
  id,
21
+ BackdropComponent,
21
22
  // Accessibility props
22
23
  accessibilityLabel,
23
24
  accessibilityHint,
@@ -124,6 +125,56 @@ const Dialog = forwardRef<View, DialogProps>(({
124
125
  const closeButtonTextStyle = (dialogStyles.closeButtonText as any)({});
125
126
  const contentStyle = (dialogStyles.content as any)({});
126
127
 
128
+ // Style for custom backdrop wrapper (no default backdrop styling)
129
+ const customBackdropWrapperStyle = {
130
+ position: 'absolute' as const,
131
+ top: 0,
132
+ left: 0,
133
+ right: 0,
134
+ bottom: 0,
135
+ display: 'flex' as const,
136
+ alignItems: 'center' as const,
137
+ justifyContent: 'center' as const,
138
+ };
139
+
140
+ // Style for custom backdrop component container (fills entire backdrop area)
141
+ const customBackdropContainerStyle = {
142
+ position: 'absolute' as const,
143
+ top: 0,
144
+ left: 0,
145
+ right: 0,
146
+ bottom: 0,
147
+ };
148
+
149
+ const dialogContainer = (
150
+ <TouchableWithoutFeedback onPress={(e: GestureResponderEvent) => e.stopPropagation()}>
151
+ <Animated.View ref={ref as any} style={[containerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
152
+ {(title || showCloseButton) && (
153
+ <View style={headerStyle}>
154
+ {title && (
155
+ <Text style={titleStyle}>
156
+ {title}
157
+ </Text>
158
+ )}
159
+ {showCloseButton && (
160
+ <TouchableOpacity
161
+ style={closeButtonStyle}
162
+ onPress={handleClosePress}
163
+ accessibilityLabel="Close dialog"
164
+ accessibilityRole="button"
165
+ >
166
+ <Text style={closeButtonTextStyle}>×</Text>
167
+ </TouchableOpacity>
168
+ )}
169
+ </View>
170
+ )}
171
+ <View style={contentStyle}>
172
+ {children}
173
+ </View>
174
+ </Animated.View>
175
+ </TouchableWithoutFeedback>
176
+ );
177
+
127
178
  return (
128
179
  <Modal
129
180
  visible={open}
@@ -134,34 +185,18 @@ const Dialog = forwardRef<View, DialogProps>(({
134
185
  testID={testID}
135
186
  >
136
187
  <TouchableWithoutFeedback onPress={handleBackdropPress}>
137
- <Animated.View style={[backdropStyle, backdropAnimatedStyle]}>
138
- <TouchableWithoutFeedback onPress={(e: GestureResponderEvent) => e.stopPropagation()}>
139
- <Animated.View ref={ref as any} style={[containerStyle, style, containerAnimatedStyle]} nativeID={id} {...nativeA11yProps}>
140
- {(title || showCloseButton) && (
141
- <View style={headerStyle}>
142
- {title && (
143
- <Text style={titleStyle}>
144
- {title}
145
- </Text>
146
- )}
147
- {showCloseButton && (
148
- <TouchableOpacity
149
- style={closeButtonStyle}
150
- onPress={handleClosePress}
151
- accessibilityLabel="Close dialog"
152
- accessibilityRole="button"
153
- >
154
- <Text style={closeButtonTextStyle}>×</Text>
155
- </TouchableOpacity>
156
- )}
157
- </View>
158
- )}
159
- <View style={contentStyle}>
160
- {children}
161
- </View>
188
+ {BackdropComponent ? (
189
+ <View style={customBackdropWrapperStyle}>
190
+ <Animated.View style={[customBackdropContainerStyle, backdropAnimatedStyle]}>
191
+ <BackdropComponent isVisible={open} />
162
192
  </Animated.View>
163
- </TouchableWithoutFeedback>
164
- </Animated.View>
193
+ {dialogContainer}
194
+ </View>
195
+ ) : (
196
+ <Animated.View style={[backdropStyle, backdropAnimatedStyle]}>
197
+ {dialogContainer}
198
+ </Animated.View>
199
+ )}
165
200
  </TouchableWithoutFeedback>
166
201
  </Modal>
167
202
  );
@@ -24,6 +24,7 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
24
24
  style,
25
25
  testID,
26
26
  id,
27
+ BackdropComponent,
27
28
  // Accessibility props
28
29
  accessibilityLabel,
29
30
  accessibilityHint,
@@ -154,7 +155,73 @@ const Dialog = forwardRef<HTMLDivElement, DialogProps>(({
154
155
 
155
156
  const mergedBackdropRef = useMergeRefs(ref, backdropProps.ref);
156
157
 
157
- const dialogContent = (
158
+ // Styles for the custom backdrop wrapper (no default backdrop styling)
159
+ const customBackdropWrapperStyle: React.CSSProperties = {
160
+ position: 'fixed',
161
+ top: 0,
162
+ left: 0,
163
+ right: 0,
164
+ bottom: 0,
165
+ display: 'flex',
166
+ alignItems: 'center',
167
+ justifyContent: 'center',
168
+ zIndex: 1000,
169
+ };
170
+
171
+ // Styles for the custom backdrop component container (fills the entire backdrop area)
172
+ const customBackdropStyle: React.CSSProperties = {
173
+ position: 'absolute',
174
+ top: 0,
175
+ left: 0,
176
+ right: 0,
177
+ bottom: 0,
178
+ };
179
+
180
+ const dialogContent = BackdropComponent ? (
181
+ <div
182
+ ref={mergedBackdropRef}
183
+ style={customBackdropWrapperStyle}
184
+ onClick={handleBackdropClick}
185
+ data-testid={testID}
186
+ >
187
+ <div style={customBackdropStyle}>
188
+ <BackdropComponent isVisible={isVisible} />
189
+ </div>
190
+ <div
191
+ {...containerProps}
192
+ {...ariaProps}
193
+ ref={dialogRef}
194
+ id={dialogId}
195
+ role="dialog"
196
+ aria-modal="true"
197
+ aria-labelledby={title ? titleId : undefined}
198
+ onClick={(e) => e.stopPropagation()}
199
+ >
200
+ {(title || showCloseButton) && (
201
+ <div {...headerProps}>
202
+ {title && (
203
+ <h2 {...titleProps} id={titleId}>
204
+ {title}
205
+ </h2>
206
+ )}
207
+ {showCloseButton && (
208
+ <button
209
+ {...closeButtonProps}
210
+ onClick={handleCloseClick}
211
+ aria-label="Close dialog"
212
+ type="button"
213
+ >
214
+ <Icon name="close" />
215
+ </button>
216
+ )}
217
+ </div>
218
+ )}
219
+ <div {...contentProps}>
220
+ {children}
221
+ </div>
222
+ </div>
223
+ </div>
224
+ ) : (
158
225
  <div
159
226
  {...backdropProps}
160
227
  ref={mergedBackdropRef}
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ReactNode, ComponentType } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
  import { BaseProps } from '../utils/viewStyleProps';
4
4
  import { InteractiveAccessibilityProps } from '../utils/accessibility';
@@ -8,6 +8,16 @@ export type DialogSizeVariant = 'sm' | 'md' | 'lg' | 'fullscreen';
8
8
  export type DialogType = 'default' | 'alert' | 'confirmation';
9
9
  export type DialogAnimationType = 'slide' | 'fade' | 'none';
10
10
 
11
+ /**
12
+ * Props passed to custom backdrop components
13
+ */
14
+ export interface BackdropComponentProps {
15
+ /**
16
+ * Whether the dialog is visible (for animation purposes)
17
+ */
18
+ isVisible: boolean;
19
+ }
20
+
11
21
  /**
12
22
  * Modal dialog component for focused interactions requiring user attention.
13
23
  * Supports multiple sizes, animation types, and configurable close behaviors.
@@ -72,4 +82,11 @@ export interface DialogProps extends BaseProps, InteractiveAccessibilityProps {
72
82
  * Test ID for testing
73
83
  */
74
84
  testID?: string;
85
+
86
+ /**
87
+ * Custom backdrop component that replaces the default backdrop.
88
+ * The component will be rendered as a full-screen overlay behind the dialog.
89
+ * It receives isVisible for animation coordination.
90
+ */
91
+ BackdropComponent?: ComponentType<BackdropComponentProps>;
75
92
  }
@@ -26,9 +26,13 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
26
26
  }, ref) => {
27
27
  const insets = useSafeAreaInsets();
28
28
 
29
+ // Handle 'transparent' background separately since it's not a surface color key
30
+ // The $surface iterator only expands to actual surface color keys
31
+ const backgroundVariant = background === 'transparent' ? undefined : background;
32
+
29
33
  // Set active variants for this render
30
34
  screenStyles.useVariants({
31
- background,
35
+ background: backgroundVariant,
32
36
  safeArea,
33
37
  gap,
34
38
  padding,
@@ -5,6 +5,7 @@ import { StyleSheet } from 'react-native-unistyles';
5
5
  import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
6
  import type { Theme as BaseTheme } from '@idealyst/theme';
7
7
  import { ViewStyleSize } from '../utils/viewStyleProps';
8
+ import type { Surface } from '@idealyst/theme';
8
9
 
9
10
  // Required: Unistyles must see StyleSheet usage in original source to process this file
10
11
  void StyleSheet;
@@ -12,7 +13,8 @@ void StyleSheet;
12
13
  // Wrap theme for $iterator support
13
14
  type Theme = ThemeStyleWrapper<BaseTheme>;
14
15
 
15
- type ScreenBackground = 'screen' | 'primary' | 'secondary' | 'tertiary' | 'inverse' | 'inverse-secondary' | 'inverse-tertiary' | 'transparent';
16
+ // Background type is derived from Surface color keys plus 'transparent'
17
+ type ScreenBackground = Surface | 'transparent';
16
18
 
17
19
  export type ScreenDynamicProps = {
18
20
  background?: ScreenBackground;
@@ -27,23 +29,19 @@ export type ScreenDynamicProps = {
27
29
  };
28
30
 
29
31
  /**
30
- * Screen styles with $iterator expansion for spacing variants.
32
+ * Screen styles with $iterator expansion for spacing and background variants.
31
33
  *
32
34
  * NOTE: Top-level theme access required for Unistyles reactivity.
35
+ * The background variant uses $surface iterator to expand to all surface color keys.
33
36
  */
34
37
  export const screenStyles = defineStyle('Screen', (theme: Theme) => ({
35
38
  screen: (_props: ScreenDynamicProps) => ({
36
39
  flex: 1,
37
40
  variants: {
41
+ // $iterator expands for each surface color key
38
42
  background: {
39
- screen: { backgroundColor: theme.colors.surface.screen },
40
- primary: { backgroundColor: theme.colors.surface.primary },
41
- secondary: { backgroundColor: theme.colors.surface.secondary },
42
- tertiary: { backgroundColor: theme.colors.surface.tertiary },
43
- inverse: { backgroundColor: theme.colors.surface.inverse },
44
- 'inverse-secondary': { backgroundColor: theme.colors.surface['inverse-secondary'] },
45
- 'inverse-tertiary': { backgroundColor: theme.colors.surface['inverse-tertiary'] },
46
- transparent: { backgroundColor: 'transparent' },
43
+ backgroundColor: theme.colors.$surface,
44
+ // 'transparent' is handled separately via compound variant or default
47
45
  },
48
46
  safeArea: {
49
47
  true: {},
@@ -81,15 +79,9 @@ export const screenStyles = defineStyle('Screen', (theme: Theme) => ({
81
79
 
82
80
  screenContent: (_props: ScreenDynamicProps) => ({
83
81
  variants: {
82
+ // $iterator expands for each surface color key
84
83
  background: {
85
- screen: { backgroundColor: theme.colors.surface.screen },
86
- primary: { backgroundColor: theme.colors.surface.primary },
87
- secondary: { backgroundColor: theme.colors.surface.secondary },
88
- tertiary: { backgroundColor: theme.colors.surface.tertiary },
89
- inverse: { backgroundColor: theme.colors.surface.inverse },
90
- 'inverse-secondary': { backgroundColor: theme.colors.surface['inverse-secondary'] },
91
- 'inverse-tertiary': { backgroundColor: theme.colors.surface['inverse-tertiary'] },
92
- transparent: { backgroundColor: 'transparent' },
84
+ backgroundColor: theme.colors.$surface,
93
85
  },
94
86
  safeArea: {
95
87
  true: {},
@@ -29,8 +29,12 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
29
29
  }, ref) => {
30
30
  const layoutRef = useWebLayout<HTMLDivElement>(onLayout);
31
31
 
32
+ // Handle 'transparent' background separately since it's not a surface color key
33
+ // The $surface iterator only expands to actual surface color keys
34
+ const backgroundVariant = background === 'transparent' ? undefined : background;
35
+
32
36
  screenStyles.useVariants({
33
- background,
37
+ background: backgroundVariant,
34
38
  safeArea,
35
39
  gap,
36
40
  padding,
@@ -47,9 +47,13 @@ const View = forwardRef<IdealystElement, ViewProps>(({
47
47
  id,
48
48
  onLayout,
49
49
  }, ref) => {
50
+ // Handle 'transparent' background separately since it's not a surface color key
51
+ // The $surface iterator only expands to actual surface color keys
52
+ const backgroundVariant = background === 'transparent' ? undefined : background;
53
+
50
54
  // Set active variants for this render
51
55
  viewStyles.useVariants({
52
- background,
56
+ background: backgroundVariant,
53
57
  radius,
54
58
  border,
55
59
  gap,
@@ -29,10 +29,12 @@ export type ViewVariants = {
29
29
  export type ViewDynamicProps = Partial<ViewVariants>;
30
30
 
31
31
  /**
32
- * View styles with $iterator expansion for spacing variants.
32
+ * View styles with $iterator expansion for spacing and background variants.
33
33
  *
34
34
  * NOTE: At least one top-level theme access is required for Unistyles to trace
35
35
  * theme dependencies. We use a transparent borderColor as a marker.
36
+ *
37
+ * The background variant uses $surface iterator to expand to all surface color keys.
36
38
  */
37
39
  export const viewStyles = defineStyle('View', (theme: Theme) => ({
38
40
  view: (_props: ViewDynamicProps) => ({
@@ -41,14 +43,10 @@ export const viewStyles = defineStyle('View', (theme: Theme) => ({
41
43
  borderColor: theme.colors.border.primary,
42
44
  borderWidth: 0,
43
45
  variants: {
46
+ // $iterator expands for each surface color key
44
47
  background: {
45
- transparent: { backgroundColor: 'transparent' },
46
- primary: { backgroundColor: theme.colors.surface.primary },
47
- secondary: { backgroundColor: theme.colors.surface.secondary },
48
- tertiary: { backgroundColor: theme.colors.surface.tertiary },
49
- inverse: { backgroundColor: theme.colors.surface.inverse },
50
- 'inverse-secondary': { backgroundColor: theme.colors.surface['inverse-secondary'] },
51
- 'inverse-tertiary': { backgroundColor: theme.colors.surface['inverse-tertiary'] },
48
+ backgroundColor: theme.colors.$surface,
49
+ // 'transparent' is handled as a special case
52
50
  },
53
51
  radius: {
54
52
  none: { borderRadius: 0 },
@@ -36,8 +36,12 @@ const View = forwardRef<IdealystElement, ViewProps>(({
36
36
  }, ref) => {
37
37
  const layoutRef = useWebLayout<HTMLDivElement>(onLayout);
38
38
 
39
+ // Handle 'transparent' background separately since it's not a surface color key
40
+ // The $surface iterator only expands to actual surface color keys
41
+ const backgroundVariant = background === 'transparent' ? undefined : background;
42
+
39
43
  viewStyles.useVariants({
40
- background,
44
+ background: backgroundVariant,
41
45
  radius,
42
46
  border,
43
47
  gap,
@@ -1,11 +1,30 @@
1
1
  import { useState } from 'react';
2
2
  import { Screen, View, Button, Text, Dialog } from '../index';
3
+ import { BlurView } from '@idealyst/blur';
4
+ import type { BackdropComponentProps } from '../Dialog/types';
5
+
6
+ /**
7
+ * Custom blur backdrop component for dialogs.
8
+ * Uses @idealyst/blur to create a frosted glass effect over the content.
9
+ */
10
+ const BlurBackdrop = ({ isVisible }: BackdropComponentProps) => (
11
+ <BlurView
12
+ intensity={isVisible ? 80 : 0}
13
+ blurType="dark"
14
+ style={{
15
+ flex: 1,
16
+ width: '100%',
17
+ height: '100%',
18
+ }}
19
+ />
20
+ );
3
21
 
4
22
  export const DialogExamples = () => {
5
23
  const [basicOpen, setBasicOpen] = useState(false);
6
24
  const [alertOpen, setAlertOpen] = useState(false);
7
25
  const [confirmationOpen, setConfirmationOpen] = useState(false);
8
26
  const [sizesOpen, setSizesOpen] = useState<string | null>(null);
27
+ const [blurBackdropOpen, setBlurBackdropOpen] = useState(false);
9
28
 
10
29
  return (
11
30
  <Screen background="primary" padding="lg">
@@ -22,7 +41,7 @@ export const DialogExamples = () => {
22
41
  </Button>
23
42
  <Dialog
24
43
  open={basicOpen}
25
- onOpenChange={setBasicOpen}
44
+ onClose={() => setBasicOpen(false)}
26
45
  title="Basic Dialog"
27
46
  >
28
47
  <Text>This is a basic dialog with a title and some content.</Text>
@@ -53,7 +72,7 @@ export const DialogExamples = () => {
53
72
  {/* Alert Dialog */}
54
73
  <Dialog
55
74
  open={alertOpen}
56
- onOpenChange={setAlertOpen}
75
+ onClose={() => setAlertOpen(false)}
57
76
  title="Important Alert"
58
77
  type="alert"
59
78
  >
@@ -72,7 +91,7 @@ export const DialogExamples = () => {
72
91
  {/* Confirmation Dialog */}
73
92
  <Dialog
74
93
  open={confirmationOpen}
75
- onOpenChange={setConfirmationOpen}
94
+ onClose={() => setConfirmationOpen(false)}
76
95
  title="Confirm Action"
77
96
  type="confirmation"
78
97
  closeOnBackdropClick={false}
@@ -114,7 +133,7 @@ export const DialogExamples = () => {
114
133
  {sizesOpen && (
115
134
  <Dialog
116
135
  open={!!sizesOpen}
117
- onOpenChange={() => setSizesOpen(null)}
136
+ onClose={() => setSizesOpen(null)}
118
137
  title={`${sizesOpen === 'sm' ? 'Small' : sizesOpen === 'md' ? 'Medium' : 'Large'} Dialog`}
119
138
  size={sizesOpen as 'sm' | 'md' | 'lg'}
120
139
  >
@@ -135,6 +154,41 @@ export const DialogExamples = () => {
135
154
  </View>
136
155
 
137
156
 
157
+ {/* Custom Blur Backdrop */}
158
+ <View gap="md">
159
+ <Text typography="subtitle1">Custom Blur Backdrop</Text>
160
+ <Text typography="caption" color="secondary">
161
+ Use the BackdropComponent prop to create a custom blur backdrop using @idealyst/blur.
162
+ </Text>
163
+ <Button onPress={() => setBlurBackdropOpen(true)}>
164
+ Open Dialog with Blur Backdrop
165
+ </Button>
166
+ <Dialog
167
+ open={blurBackdropOpen}
168
+ onClose={() => setBlurBackdropOpen(false)}
169
+ title="Blur Backdrop Dialog"
170
+ BackdropComponent={BlurBackdrop}
171
+ >
172
+ <Text>
173
+ This dialog uses a custom backdrop component with @idealyst/blur to create
174
+ a frosted glass effect over the background content.
175
+ </Text>
176
+ <View gap="md" style={{ marginTop: 16 }}>
177
+ <Text typography="caption" color="secondary">
178
+ The blur intensity animates based on the isVisible prop passed to the
179
+ BackdropComponent.
180
+ </Text>
181
+ <Button
182
+ type="contained"
183
+ intent="primary"
184
+ onPress={() => setBlurBackdropOpen(false)}
185
+ >
186
+ Close
187
+ </Button>
188
+ </View>
189
+ </Dialog>
190
+ </View>
191
+
138
192
  {/* Dialog Options */}
139
193
  <View gap="md">
140
194
  <Text typography="subtitle1">Dialog Options</Text>
@@ -150,6 +204,9 @@ export const DialogExamples = () => {
150
204
  <Text typography="caption" color="secondary">
151
205
  • Focus management: Automatic focus trapping and restoration (web only)
152
206
  </Text>
207
+ <Text typography="caption" color="secondary">
208
+ • Custom backdrop: Use BackdropComponent prop for custom effects like blur
209
+ </Text>
153
210
  </View>
154
211
  </View>
155
212
  </Screen>