feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,505 @@
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
import * as React from 'react';
import { Animated, Dimensions, Easing, I18nManager, Keyboard, Platform, ScrollView, StyleSheet, View, Pressable } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import MenuItem from './MenuItem';
import { useInternalTheme } from '../../core/theming';
import { ElevationLevels } from '../../types';
import { addEventListener } from '../../utils/addEventListener';
import { BackHandler } from '../../utils/BackHandler/BackHandler';
import Portal from '../Portal/Portal';
import Surface from '../Surface';
// Minimum padding between the edge of the screen and the menu
const SCREEN_INDENT = 8;
// From https://material.io/design/motion/speed.html#duration
const ANIMATION_DURATION = 250;
// From the 'Standard easing' section of https://material.io/design/motion/speed.html#easing
const EASING = Easing.bezier(0.4, 0, 0.2, 1);
const WINDOW_LAYOUT = Dimensions.get('window');
const DEFAULT_ELEVATION = 2;
export const ELEVATION_LEVELS_MAP = Object.values(ElevationLevels);
const DEFAULT_MODE = 'elevated';
const focusFirstDOMNode = el => {
if (el && Platform.OS === 'web') {
// When in the browser, we want to focus the first focusable item on toggle
// For example, when menu is shown, focus the first item in the menu
// And when menu is dismissed, send focus back to the button to resume tabbing
if (el instanceof HTMLElement) {
var _el$querySelector;
(_el$querySelector = el.querySelector(
// This is a rough list of selectors that can be focused
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')) === null || _el$querySelector === void 0 || _el$querySelector.focus();
}
}
};
const isCoordinate = anchor => ! /*#__PURE__*/React.isValidElement(anchor) && typeof (anchor === null || anchor === void 0 ? void 0 : anchor.x) === 'number' && typeof (anchor === null || anchor === void 0 ? void 0 : anchor.y) === 'number';
const isBrowser = () => Platform.OS === 'web' && 'document' in global;
/**
* Menus display a list of choices on temporary elevated surfaces. Their placement varies based on the element that opens them.
*
* ## Usage
* ```js
* import * as React from 'react';
* import { View } from 'react-native';
* import { Button, Menu, Divider, PaperProvider } from 'react-native-paper';
*
* const MyComponent = () => {
* const [visible, setVisible] = React.useState(false);
*
* const openMenu = () => setVisible(true);
*
* const closeMenu = () => setVisible(false);
*
* return (
* <PaperProvider>
* <View
* style={{
* paddingTop: 50,
* flexDirection: 'row',
* justifyContent: 'center',
* }}>
* <Menu
* visible={visible}
* onDismiss={closeMenu}
* anchor={<Button onPress={openMenu}>Show menu</Button>}>
* <Menu.Item onPress={() => {}} title="Item 1" />
* <Menu.Item onPress={() => {}} title="Item 2" />
* <Divider />
* <Menu.Item onPress={() => {}} title="Item 3" />
* </Menu>
* </View>
* </PaperProvider>
* );
* };
*
* export default MyComponent;
* ```
*
* ### Note
* When using `Menu` within a React Native's `Modal` component, you need to wrap all
* `Modal` contents within a `PaperProvider` in order for the menu to show. This
* wrapping is not necessary if you use Paper's `Modal` instead.
*/
const Menu = ({
visible,
statusBarHeight,
overlayAccessibilityLabel = 'Close menu',
testID = 'menu',
anchor,
onDismiss,
anchorPosition,
contentStyle,
style,
elevation = DEFAULT_ELEVATION,
mode = DEFAULT_MODE,
children,
theme: themeOverrides,
keyboardShouldPersistTaps
}) => {
const theme = useInternalTheme(themeOverrides);
const insets = useSafeAreaInsets();
const [rendered, setRendered] = React.useState(visible);
const [left, setLeft] = React.useState(0);
const [top, setTop] = React.useState(0);
const [menuLayout, setMenuLayout] = React.useState({
width: 0,
height: 0
});
const [anchorLayout, setAnchorLayout] = React.useState({
width: 0,
height: 0
});
const [windowLayout, setWindowLayout] = React.useState({
width: WINDOW_LAYOUT.width,
height: WINDOW_LAYOUT.height
});
const opacityAnimationRef = React.useRef(new Animated.Value(0));
const scaleAnimationRef = React.useRef(new Animated.ValueXY({
x: 0,
y: 0
}));
const keyboardHeightRef = React.useRef(0);
const prevVisible = React.useRef(null);
const anchorRef = React.useRef(null);
const menuRef = React.useRef(null);
const prevRendered = React.useRef(false);
const keyboardDidShow = React.useCallback(e => {
const keyboardHeight = e.endCoordinates.height;
keyboardHeightRef.current = keyboardHeight;
}, []);
const keyboardDidHide = React.useCallback(() => {
keyboardHeightRef.current = 0;
}, []);
const keyboardDidShowListenerRef = React.useRef(undefined);
const keyboardDidHideListenerRef = React.useRef(undefined);
const backHandlerSubscriptionRef = React.useRef(undefined);
const dimensionsSubscriptionRef = React.useRef(undefined);
const handleDismiss = React.useCallback(() => {
if (visible) {
onDismiss === null || onDismiss === void 0 || onDismiss();
}
}, [onDismiss, visible]);
const handleKeypress = React.useCallback(e => {
if (e.key === 'Escape') {
onDismiss === null || onDismiss === void 0 || onDismiss();
}
}, [onDismiss]);
const removeListeners = React.useCallback(() => {
var _backHandlerSubscript, _dimensionsSubscripti;
(_backHandlerSubscript = backHandlerSubscriptionRef.current) === null || _backHandlerSubscript === void 0 || _backHandlerSubscript.remove();
(_dimensionsSubscripti = dimensionsSubscriptionRef.current) === null || _dimensionsSubscripti === void 0 || _dimensionsSubscripti.remove();
isBrowser() && document.removeEventListener('keyup', handleKeypress);
}, [handleKeypress]);
const attachListeners = React.useCallback(() => {
backHandlerSubscriptionRef.current = addEventListener(BackHandler, 'hardwareBackPress', handleDismiss);
dimensionsSubscriptionRef.current = addEventListener(Dimensions, 'change', handleDismiss);
Platform.OS === 'web' && document.addEventListener('keyup', handleKeypress);
}, [handleDismiss, handleKeypress]);
const measureMenuLayout = () => new Promise(resolve => {
if (menuRef.current) {
menuRef.current.measureInWindow((x, y, width, height) => {
resolve({
x,
y,
width,
height
});
});
}
});
const measureAnchorLayout = React.useCallback(() => new Promise(resolve => {
if (isCoordinate(anchor)) {
resolve({
x: anchor.x,
y: anchor.y,
width: 0,
height: 0
});
return;
}
if (anchorRef.current) {
anchorRef.current.measureInWindow((x, y, width, height) => {
resolve({
x,
y,
width,
height
});
});
}
}), [anchor]);
const show = React.useCallback(async () => {
const windowLayoutResult = Dimensions.get('window');
const [menuLayoutResult, anchorLayoutResult] = await Promise.all([measureMenuLayout(), measureAnchorLayout()]);
// When visible is true for first render
// native views can be still not rendered and
// measureMenuLayout/measureAnchorLayout functions
// return wrong values e.g { x:0, y: 0, width: 0, height: 0 }
// so we have to wait until views are ready
// and rerun this function to show menu
if (!windowLayoutResult.width || !windowLayoutResult.height || !menuLayoutResult.width || !menuLayoutResult.height || !anchorLayoutResult.width && !isCoordinate(anchor) || !anchorLayoutResult.height && !isCoordinate(anchor)) {
requestAnimationFrame(show);
return;
}
setLeft(anchorLayoutResult.x);
setTop(anchorLayoutResult.y);
setAnchorLayout({
height: anchorLayoutResult.height,
width: anchorLayoutResult.width
});
setMenuLayout({
height: menuLayoutResult.height,
width: menuLayoutResult.width
});
setWindowLayout({
height: windowLayoutResult.height - keyboardHeightRef.current,
width: windowLayoutResult.width
});
attachListeners();
requestAnimationFrame(() => {
const {
animation
} = theme;
Animated.parallel([Animated.timing(scaleAnimationRef.current, {
toValue: {
x: menuLayoutResult.width,
y: menuLayoutResult.height
},
duration: ANIMATION_DURATION * animation.scale,
easing: EASING,
useNativeDriver: true
}), Animated.timing(opacityAnimationRef.current, {
toValue: 1,
duration: ANIMATION_DURATION * animation.scale,
easing: EASING,
useNativeDriver: true
})]).start(() => {
focusFirstDOMNode(menuRef.current);
prevRendered.current = true;
});
});
}, [anchor, attachListeners, measureAnchorLayout, theme]);
const hide = React.useCallback(() => {
removeListeners();
const {
animation
} = theme;
Animated.timing(opacityAnimationRef.current, {
toValue: 0,
duration: ANIMATION_DURATION * animation.scale,
easing: EASING,
useNativeDriver: true
}).start(() => {
setMenuLayout({
width: 0,
height: 0
});
setRendered(false);
prevRendered.current = false;
focusFirstDOMNode(anchorRef.current);
});
}, [removeListeners, theme]);
const updateVisibility = React.useCallback(async display => {
// Menu is rendered in Portal, which updates items asynchronously
// We need to do the same here so that the ref is up-to-date
await Promise.resolve().then(() => {
if (display && !prevRendered.current) {
show();
return;
}
if (!display) {
hide();
}
return;
});
}, [hide, show]);
React.useEffect(() => {
const opacityAnimation = opacityAnimationRef.current;
const scaleAnimation = scaleAnimationRef.current;
keyboardDidShowListenerRef.current = Keyboard.addListener('keyboardDidShow', keyboardDidShow);
keyboardDidHideListenerRef.current = Keyboard.addListener('keyboardDidHide', keyboardDidHide);
return () => {
var _keyboardDidShowListe, _keyboardDidHideListe;
removeListeners();
(_keyboardDidShowListe = keyboardDidShowListenerRef.current) === null || _keyboardDidShowListe === void 0 || _keyboardDidShowListe.remove();
(_keyboardDidHideListe = keyboardDidHideListenerRef.current) === null || _keyboardDidHideListe === void 0 || _keyboardDidHideListe.remove();
scaleAnimation.removeAllListeners();
opacityAnimation === null || opacityAnimation === void 0 || opacityAnimation.removeAllListeners();
};
}, [removeListeners, keyboardDidHide, keyboardDidShow]);
React.useEffect(() => {
if (prevVisible.current !== visible) {
prevVisible.current = visible;
if (visible) {
if (!rendered) {
// Mount the Portal before attempting to show.
setRendered(true);
}
} else {
// Keep the Portal mounted so the hide animation can finish.
updateVisibility(false);
}
}
}, [visible, rendered, updateVisibility]);
React.useEffect(() => {
if (rendered && visible) {
updateVisibility(true);
}
}, [rendered, visible, updateVisibility]);
// I don't know why but on Android measure function is wrong by 24
const additionalVerticalValue = Platform.select({
android: statusBarHeight ?? insets.top,
default: 0
});
// We need to translate menu while animating scale to imitate transform origin for scale animation
const positionTransforms = [];
let leftTransformation = left;
let topTransformation = !isCoordinate(anchorRef.current) && anchorPosition === 'bottom' ? top + anchorLayout.height : top;
// Check if menu fits horizontally and if not align it to right.
if (left <= windowLayout.width - menuLayout.width - SCREEN_INDENT) {
positionTransforms.push({
translateX: scaleAnimationRef.current.x.interpolate({
inputRange: [0, menuLayout.width],
outputRange: [-(menuLayout.width / 2), 0]
})
});
// Check if menu position has enough space from left side
if (leftTransformation < SCREEN_INDENT) {
leftTransformation = SCREEN_INDENT;
}
} else {
positionTransforms.push({
translateX: scaleAnimationRef.current.x.interpolate({
inputRange: [0, menuLayout.width],
outputRange: [menuLayout.width / 2, 0]
})
});
leftTransformation += anchorLayout.width - menuLayout.width;
const right = leftTransformation + menuLayout.width;
// Check if menu position has enough space from right side
if (right > windowLayout.width - SCREEN_INDENT) {
leftTransformation = windowLayout.width - SCREEN_INDENT - menuLayout.width;
}
}
// If the menu is larger than available vertical space,
// calculate the height of scrollable view
let scrollableMenuHeight = 0;
// Check if the menu should be scrollable
if (
// Check if the menu overflows from bottom side
topTransformation >= windowLayout.height - menuLayout.height - SCREEN_INDENT - additionalVerticalValue &&
// And bottom side of the screen has more space than top side
topTransformation <= windowLayout.height - topTransformation) {
// Scrollable menu should be below the anchor (expands downwards)
scrollableMenuHeight = windowLayout.height - topTransformation - SCREEN_INDENT - additionalVerticalValue;
} else if (
// Check if the menu overflows from bottom side
topTransformation >= windowLayout.height - menuLayout.height - SCREEN_INDENT - additionalVerticalValue &&
// And top side of the screen has more space than bottom side
topTransformation >= windowLayout.height - top &&
// And menu overflows from top side
topTransformation <= menuLayout.height - anchorLayout.height + SCREEN_INDENT - additionalVerticalValue) {
// Scrollable menu should be above the anchor (expands upwards)
scrollableMenuHeight = topTransformation + anchorLayout.height - SCREEN_INDENT + additionalVerticalValue;
}
// Scrollable menu max height
scrollableMenuHeight = scrollableMenuHeight > windowLayout.height - 2 * SCREEN_INDENT ? windowLayout.height - 2 * SCREEN_INDENT : scrollableMenuHeight;
// Menu is typically positioned below the element that generates it
// So first check if it fits below the anchor (expands downwards)
if (
// Check if menu fits vertically
topTransformation <= windowLayout.height - menuLayout.height - SCREEN_INDENT - additionalVerticalValue ||
// Or if the menu overflows from bottom side
topTransformation >= windowLayout.height - menuLayout.height - SCREEN_INDENT - additionalVerticalValue &&
// And bottom side of the screen has more space than top side
topTransformation <= windowLayout.height - topTransformation) {
positionTransforms.push({
translateY: scaleAnimationRef.current.y.interpolate({
inputRange: [0, menuLayout.height],
outputRange: [-((scrollableMenuHeight || menuLayout.height) / 2), 0]
})
});
// Check if menu position has enough space from top side
if (topTransformation < SCREEN_INDENT) {
topTransformation = SCREEN_INDENT;
}
} else {
positionTransforms.push({
translateY: scaleAnimationRef.current.y.interpolate({
inputRange: [0, menuLayout.height],
outputRange: [(scrollableMenuHeight || menuLayout.height) / 2, 0]
})
});
topTransformation += anchorLayout.height - (scrollableMenuHeight || menuLayout.height);
const bottom = topTransformation + (scrollableMenuHeight || menuLayout.height) + additionalVerticalValue;
// Check if menu position has enough space from bottom side
if (bottom > windowLayout.height - SCREEN_INDENT) {
topTransformation = scrollableMenuHeight === windowLayout.height - 2 * SCREEN_INDENT ? -SCREEN_INDENT * 2 : windowLayout.height - menuLayout.height - SCREEN_INDENT - additionalVerticalValue;
}
}
const shadowMenuContainerStyle = {
opacity: opacityAnimationRef.current,
transform: [{
scaleX: scaleAnimationRef.current.x.interpolate({
inputRange: [0, menuLayout.width],
outputRange: [0, 1]
})
}, {
scaleY: scaleAnimationRef.current.y.interpolate({
inputRange: [0, menuLayout.height],
outputRange: [0, 1]
})
}],
borderRadius: theme.roundness,
...(!theme.isV3 && {
elevation: 8
}),
...(scrollableMenuHeight ? {
height: scrollableMenuHeight
} : {})
};
const positionStyle = {
top: isCoordinate(anchor) ? topTransformation : topTransformation + additionalVerticalValue,
...(I18nManager.getConstants().isRTL ? {
right: leftTransformation
} : {
left: leftTransformation
})
};
const pointerEvents = visible ? 'box-none' : 'none';
return /*#__PURE__*/React.createElement(View, {
ref: ref => {
anchorRef.current = ref;
},
collapsable: false
}, isCoordinate(anchor) ? null : anchor, rendered ? /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(Pressable, {
accessibilityLabel: overlayAccessibilityLabel,
accessibilityRole: "button",
onPress: onDismiss,
pointerEvents: visible ? 'auto' : 'none',
style: styles.pressableOverlay
}), /*#__PURE__*/React.createElement(View, {
ref: ref => {
menuRef.current = ref;
},
collapsable: false,
accessibilityViewIsModal: visible,
style: [styles.wrapper, positionStyle, style],
pointerEvents: pointerEvents,
onAccessibilityEscape: onDismiss,
testID: `${testID}-view`
}, /*#__PURE__*/React.createElement(Animated.View, {
pointerEvents: pointerEvents,
style: {
transform: positionTransforms
}
}, /*#__PURE__*/React.createElement(Surface, _extends({
mode: mode,
pointerEvents: pointerEvents,
style: [styles.shadowMenuContainer, shadowMenuContainerStyle, theme.isV3 && {
backgroundColor: theme.colors.elevation[ELEVATION_LEVELS_MAP[elevation]]
}, contentStyle]
}, theme.isV3 && {
elevation
}, {
testID: `${testID}-surface`,
theme: theme,
container: true
}), scrollableMenuHeight && /*#__PURE__*/React.createElement(ScrollView, {
keyboardShouldPersistTaps: keyboardShouldPersistTaps
}, children) || /*#__PURE__*/React.createElement(React.Fragment, null, children))))) : null);
};
Menu.Item = MenuItem;
const styles = StyleSheet.create({
wrapper: {
position: 'absolute'
},
shadowMenuContainer: {
opacity: 0,
paddingVertical: 8
},
pressableOverlay: {
...Platform.select({
web: {
cursor: 'auto'
}
}),
...StyleSheet.absoluteFill,
width: '100%'
}
});
export default Menu;
//# sourceMappingURL=Menu.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,159 @@
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import { getContentMaxWidth, getMenuItemColor, MAX_WIDTH, MIN_WIDTH } from './utils';
import { useInternalTheme } from '../../core/theming';
import Icon from '../Icon';
import TouchableRipple from '../TouchableRipple/TouchableRipple';
import Text from '../Typography/Text';
/**
* A component to show a single list item inside a Menu.
*
* ## Usage
* ```js
* import * as React from 'react';
* import { View } from 'react-native';
* import { Menu } from 'react-native-paper';
*
* const MyComponent = () => (
* <View style={{ flex: 1 }}>
* <Menu.Item leadingIcon="redo" onPress={() => {}} title="Redo" />
* <Menu.Item leadingIcon="undo" onPress={() => {}} title="Undo" />
* <Menu.Item leadingIcon="content-cut" onPress={() => {}} title="Cut" disabled />
* <Menu.Item leadingIcon="content-copy" onPress={() => {}} title="Copy" disabled />
* <Menu.Item leadingIcon="content-paste" onPress={() => {}} title="Paste" />
* </View>
* );
*
* export default MyComponent;
* ```
*/
const MenuItem = ({
leadingIcon,
trailingIcon,
dense,
title,
disabled,
background,
onPress,
style,
containerStyle,
contentStyle,
titleStyle,
rippleColor: customRippleColor,
testID = 'menu-item',
accessibilityLabel,
accessibilityState,
theme: themeOverrides,
titleMaxFontSizeMultiplier = 1.5,
hitSlop
}) => {
const theme = useInternalTheme(themeOverrides);
const {
titleColor,
iconColor,
rippleColor
} = getMenuItemColor({
theme,
disabled,
customRippleColor
});
const {
isV3
} = theme;
const containerPadding = isV3 ? 12 : 8;
const iconWidth = isV3 ? 24 : 40;
const minWidth = MIN_WIDTH - (isV3 ? 12 : 16);
const maxWidth = getContentMaxWidth({
isV3,
iconWidth,
leadingIcon,
trailingIcon
});
const titleTextStyle = {
color: titleColor,
...(isV3 ? theme.fonts.bodyLarge : {})
};
const newAccessibilityState = {
...accessibilityState,
disabled
};
return /*#__PURE__*/React.createElement(TouchableRipple, {
style: [styles.container, {
paddingHorizontal: containerPadding
}, dense && styles.md3DenseContainer, style],
onPress: onPress,
disabled: disabled,
testID: testID,
background: background,
accessibilityLabel: accessibilityLabel,
accessibilityRole: "menuitem",
accessibilityState: newAccessibilityState,
rippleColor: rippleColor,
hitSlop: hitSlop
}, /*#__PURE__*/React.createElement(View, {
style: [styles.row, containerStyle]
}, leadingIcon ? /*#__PURE__*/React.createElement(View, {
style: [!isV3 && styles.item, {
width: iconWidth
}],
pointerEvents: "box-none"
}, /*#__PURE__*/React.createElement(Icon, {
source: leadingIcon,
size: 24,
color: iconColor
})) : null, /*#__PURE__*/React.createElement(View, {
style: [!isV3 && styles.item, styles.content, {
minWidth,
maxWidth
}, isV3 && (leadingIcon ? styles.md3LeadingIcon : styles.md3WithoutLeadingIcon), contentStyle],
pointerEvents: "none"
}, /*#__PURE__*/React.createElement(Text, {
variant: "bodyLarge",
selectable: false,
numberOfLines: 1,
testID: `${testID}-title`,
style: [!isV3 && styles.title, titleTextStyle, titleStyle],
maxFontSizeMultiplier: titleMaxFontSizeMultiplier
}, title)), isV3 && trailingIcon ? /*#__PURE__*/React.createElement(View, {
style: [!isV3 && styles.item, {
width: iconWidth
}],
pointerEvents: "box-none"
}, /*#__PURE__*/React.createElement(Icon, {
source: trailingIcon,
size: 24,
color: iconColor
})) : null));
};
MenuItem.displayName = 'Menu.Item';
const styles = StyleSheet.create({
container: {
minWidth: MIN_WIDTH,
maxWidth: MAX_WIDTH,
height: 48,
justifyContent: 'center'
},
md3DenseContainer: {
height: 32
},
row: {
flexDirection: 'row'
},
title: {
fontSize: 16
},
item: {
marginHorizontal: 8
},
content: {
justifyContent: 'center'
},
md3LeadingIcon: {
marginLeft: 12
},
md3WithoutLeadingIcon: {
marginLeft: 4
}
});
export default MenuItem;
//# sourceMappingURL=MenuItem.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["React","StyleSheet","View","getContentMaxWidth","getMenuItemColor","MAX_WIDTH","MIN_WIDTH","useInternalTheme","Icon","TouchableRipple","Text","MenuItem","leadingIcon","trailingIcon","dense","title","disabled","background","onPress","style","containerStyle","contentStyle","titleStyle","rippleColor","customRippleColor","testID","accessibilityLabel","accessibilityState","theme","themeOverrides","titleMaxFontSizeMultiplier","hitSlop","titleColor","iconColor","isV3","containerPadding","iconWidth","minWidth","maxWidth","titleTextStyle","color","fonts","bodyLarge","newAccessibilityState","createElement","styles","container","paddingHorizontal","md3DenseContainer","accessibilityRole","row","item","width","pointerEvents","source","size","content","md3LeadingIcon","md3WithoutLeadingIcon","variant","selectable","numberOfLines","maxFontSizeMultiplier","displayName","create","height","justifyContent","flexDirection","fontSize","marginHorizontal","marginLeft"],"sourceRoot":"../../../../src","sources":["components/Menu/MenuItem.tsx"],"mappings":"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","ignoreList":[]}

View File

@@ -0,0 +1,87 @@
import color from 'color';
import { black, white } from '../../styles/themes/v2/colors';
export const MIN_WIDTH = 112;
export const MAX_WIDTH = 280;
const getDisabledColor = theme => {
if (theme.isV3) {
return theme.colors.onSurfaceDisabled;
}
return color(theme.dark ? white : black).alpha(0.32).rgb().string();
};
const getTitleColor = ({
theme,
disabled
}) => {
if (disabled) {
return getDisabledColor(theme);
}
if (theme.isV3) {
return theme.colors.onSurface;
}
return color(theme.colors.text).alpha(0.87).rgb().string();
};
const getIconColor = ({
theme,
disabled
}) => {
if (disabled) {
return getDisabledColor(theme);
}
if (theme.isV3) {
return theme.colors.onSurfaceVariant;
}
return color(theme.colors.text).alpha(0.54).rgb().string();
};
const getRippleColor = ({
theme,
customRippleColor
}) => {
if (customRippleColor) {
return customRippleColor;
}
if (theme.isV3) {
return color(theme.colors.onSurfaceVariant).alpha(0.12).rgb().string();
}
return undefined;
};
export const getMenuItemColor = ({
theme,
disabled,
customRippleColor
}) => {
return {
titleColor: getTitleColor({
theme,
disabled
}),
iconColor: getIconColor({
theme,
disabled
}),
rippleColor: getRippleColor({
theme,
customRippleColor
})
};
};
export const getContentMaxWidth = ({
isV3,
iconWidth,
leadingIcon,
trailingIcon
}) => {
if (isV3) {
if (leadingIcon && trailingIcon) {
return MAX_WIDTH - (2 * iconWidth + 24);
}
if (leadingIcon || trailingIcon) {
return MAX_WIDTH - (iconWidth + 24);
}
return MAX_WIDTH - 12;
}
if (leadingIcon) {
return MAX_WIDTH - (iconWidth + 48);
}
return MAX_WIDTH - 16;
};
//# sourceMappingURL=utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["color","black","white","MIN_WIDTH","MAX_WIDTH","getDisabledColor","theme","isV3","colors","onSurfaceDisabled","dark","alpha","rgb","string","getTitleColor","disabled","onSurface","text","getIconColor","onSurfaceVariant","getRippleColor","customRippleColor","undefined","getMenuItemColor","titleColor","iconColor","rippleColor","getContentMaxWidth","iconWidth","leadingIcon","trailingIcon"],"sourceRoot":"../../../../src","sources":["components/Menu/utils.ts"],"mappings":"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","ignoreList":[]}