@pallasoft/ps-mobile-components 1.13.1 → 1.14.0

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.
@@ -19,9 +19,9 @@ export const PsActionMenu = ({ children, header, isDisabled = false, items, labe
19
19
  if (!isDisabled)
20
20
  menu.open();
21
21
  },
22
- }, children: children }), menu.anchor === undefined ? null : (_jsxs(PsMenuPopup, { anchor: menu.anchor, onClose: menu.close, children: [header === undefined ? null : (_jsx(PsMenuPopupHeader, { subtitle: header.subtitle, title: header.title })), groups.map((group, index) => (_jsxs(Fragment, { children: [index > 0 || header !== undefined ? _jsx(PsMenuPopupDivider, {}) : null, PsActionMenuService.flatten([group]).map((item) => (_jsx(PsMenuPopupRow, { iconKey: item.iconKey, isChecked: item.isChecked, isDanger: item.isDanger, isDisabled: item.isDisabled, isWarning: item.isWarning, label: item.label, onPress: () => {
22
+ }, children: children }), menu.anchor === undefined ? null : (_jsxs(PsMenuPopup, { anchor: menu.anchor, onClose: menu.close, children: [header === undefined ? null : (_jsx(PsMenuPopupHeader, { subtitle: header.subtitle, title: header.title })), groups.map((group, index) => (_jsxs(Fragment, { children: [index > 0 || header !== undefined ? _jsx(PsMenuPopupDivider, {}) : null, PsActionMenuService.flatten([group]).map((item) => (_jsx(PsMenuPopupRow, { iconKey: item.iconKey, imageUri: item.imageUri, isChecked: item.isChecked, isDanger: item.isDanger, isDisabled: item.isDisabled, isWarning: item.isWarning, label: item.label, monogram: item.monogram, onPress: () => {
23
23
  menu.close();
24
24
  haptic('selection');
25
25
  item.onSelect();
26
- }, subtitle: item.subtitle }, item.id)))] }, group.id)))] }))] }));
26
+ }, subtitle: item.subtitle, tileColor: item.tileColor }, item.id)))] }, group.id)))] }))] }));
27
27
  };
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useRef, useState } from 'react';
3
+ import { PallasoftColorContrastService } from '@pallasoft/ps-design-system';
3
4
  import Svg, { ClipPath, Defs, G, Rect, Image as SvgImage, Text as SvgText } from 'react-native-svg';
4
5
  import { PsMenuTileConfig } from '../../constants/ps-menu-tile-config';
5
6
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
@@ -14,6 +15,10 @@ export const PsMenuTileDrawing = ({ onTile, request }) => {
14
15
  const isMounted = useRef(true);
15
16
  const [isLaidOut, setIsLaidOut] = useState(false);
16
17
  const [isImageLoaded, setIsImageLoaded] = useState(false);
18
+ const fill = request.tileColor === undefined ? theme.primary : request.tileColor;
19
+ const textColor = request.tileColor === undefined
20
+ ? theme.primaryContrast
21
+ : PallasoftColorContrastService.textOn(request.tileColor);
17
22
  const isReady = isLaidOut && (request.imageUri === undefined || isImageLoaded);
18
23
  useEffect(() => {
19
24
  isMounted.current = true;
@@ -29,6 +34,6 @@ export const PsMenuTileDrawing = ({ onTile, request }) => {
29
34
  if (isMounted.current)
30
35
  onTile(request.key, `${PsMenuTileConfig.dataUriPrefix}${base64}`);
31
36
  });
32
- }, [isReady, onTile, request.key, theme.primary, theme.primaryContrast]);
33
- return (_jsxs(Svg, { height: size, onLayout: () => setIsLaidOut(true), ref: ref, width: size, children: [_jsx(Defs, { children: _jsx(ClipPath, { id: clipId, children: _jsx(Rect, { height: size, rx: isCircle ? size / 2 : PsMenuTileConfig.roundedRadius, width: size }) }) }), _jsxs(G, { clipPath: `url(#${clipId})`, children: [_jsx(Rect, { fill: request.imageUri === undefined ? theme.primary : theme.white, height: size, width: size }), request.imageUri === undefined ? (_jsx(SvgText, { fill: theme.primaryContrast, fontFamily: theme.fonts.semibold, fontSize: fontSize, textAnchor: 'middle', x: size / 2, y: size / 2 + fontSize * PsMenuTileConfig.textBaseline, children: request.text })) : (_jsx(SvgImage, { height: size - inset * 2, href: { uri: request.imageUri }, onLoad: () => setIsImageLoaded(true), preserveAspectRatio: isCircle ? 'xMidYMid slice' : 'xMidYMid meet', width: size - inset * 2, x: inset, y: inset }))] })] }));
37
+ }, [isReady, onTile, request.key, fill, textColor]);
38
+ return (_jsxs(Svg, { height: size, onLayout: () => setIsLaidOut(true), ref: ref, width: size, children: [_jsx(Defs, { children: _jsx(ClipPath, { id: clipId, children: _jsx(Rect, { height: size, rx: isCircle ? size / 2 : PsMenuTileConfig.roundedRadius, width: size }) }) }), _jsxs(G, { clipPath: `url(#${clipId})`, children: [_jsx(Rect, { fill: request.imageUri === undefined ? fill : theme.white, height: size, width: size }), request.imageUri === undefined ? (_jsx(SvgText, { fill: textColor, fontFamily: theme.fonts.semibold, fontSize: fontSize, textAnchor: 'middle', x: size / 2, y: size / 2 + fontSize * PsMenuTileConfig.textBaseline, children: request.text })) : (_jsx(SvgImage, { height: size - inset * 2, href: { uri: request.imageUri }, onLoad: () => setIsImageLoaded(true), preserveAspectRatio: isCircle ? 'xMidYMid slice' : 'xMidYMid meet', width: size - inset * 2, x: inset, y: inset }))] })] }));
34
39
  };
@@ -1,12 +1,15 @@
1
1
  import type { PsMobileIconKey } from '../../constants/ps-mobile-icon-key';
2
2
  export interface PsMenuPopupRowProps {
3
3
  iconKey?: PsMobileIconKey;
4
+ imageUri?: string;
4
5
  isChecked?: boolean;
5
6
  isDanger?: boolean;
6
7
  isDisabled?: boolean;
7
8
  isWarning?: boolean;
8
9
  label: string;
10
+ monogram?: string;
9
11
  onPress: () => void;
10
12
  subtitle?: string;
13
+ tileColor?: string;
11
14
  }
12
- export declare const PsMenuPopupRow: ({ iconKey, isChecked, isDanger, isDisabled, isWarning, label, onPress, subtitle, }: PsMenuPopupRowProps) => import("react").JSX.Element;
15
+ export declare const PsMenuPopupRow: ({ iconKey, imageUri, isChecked, isDanger, isDisabled, isWarning, label, monogram, onPress, subtitle, tileColor, }: PsMenuPopupRowProps) => import("react").JSX.Element;
@@ -11,7 +11,8 @@ import { PsRippleService } from '../../services/ps-ripple-service';
11
11
  import { PsStateService } from '../../services/ps-state-service';
12
12
  import { PsIcon } from '../ps-icon/ps-icon';
13
13
  import { PsText } from '../ps-text/ps-text';
14
- export const PsMenuPopupRow = ({ iconKey, isChecked = false, isDanger = false, isDisabled = false, isWarning = false, label, onPress, subtitle, }) => {
14
+ import { PsMenuPopupTile } from './ps-menu-popup-tile';
15
+ export const PsMenuPopupRow = ({ iconKey, imageUri, isChecked = false, isDanger = false, isDisabled = false, isWarning = false, label, monogram, onPress, subtitle, tileColor, }) => {
15
16
  const theme = usePsMobileTheme();
16
17
  const scheme = usePsMaterialScheme();
17
18
  const isMaterial = PsMenuPopupStyleService.isMaterial();
@@ -30,5 +31,5 @@ export const PsMenuPopupRow = ({ iconKey, isChecked = false, isDanger = false, i
30
31
  overflow: 'hidden',
31
32
  paddingHorizontal: row.paddingHorizontal,
32
33
  };
33
- }, children: [iconKey === undefined ? null : _jsx(PsIcon, { color: tint, iconKey: iconKey }), _jsxs(View, { style: { flex: 1 }, children: [_jsx(PsText, { numberOfLines: 1, tone: isDanger ? 'danger' : 'ink', weight: PsMenuPopupStyleService.labelWeight(isMaterial, isChecked), children: label }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: isWarning ? 'warning' : 'muted', variant: 'small', children: subtitle }))] }), isChecked ? _jsx(PsIcon, { color: theme.primaryText, iconKey: PsIconKey.Check }) : null] }));
34
+ }, children: [imageUri !== undefined || monogram !== undefined ? (_jsx(PsMenuPopupTile, { imageUri: imageUri, monogram: monogram, tileColor: tileColor })) : iconKey === undefined ? null : (_jsx(PsIcon, { color: tint, iconKey: iconKey })), _jsxs(View, { style: { flex: 1 }, children: [_jsx(PsText, { numberOfLines: 1, tone: isDanger ? 'danger' : 'ink', weight: PsMenuPopupStyleService.labelWeight(isMaterial, isChecked), children: label }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: isWarning ? 'warning' : 'muted', variant: 'small', children: subtitle }))] }), isChecked ? _jsx(PsIcon, { color: theme.primaryText, iconKey: PsIconKey.Check }) : null] }));
34
35
  };
@@ -0,0 +1,6 @@
1
+ export interface PsMenuPopupTileProps {
2
+ imageUri?: string;
3
+ monogram?: string;
4
+ tileColor?: string;
5
+ }
6
+ export declare const PsMenuPopupTile: ({ imageUri, monogram, tileColor }: PsMenuPopupTileProps) => import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PallasoftColorContrastService } from '@pallasoft/ps-design-system';
3
+ import { Image, View } from 'react-native';
4
+ import { PsMenuPopupConfig } from '../../constants/ps-menu-popup-config';
5
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
6
+ import { PsMenuTileService } from '../../services/ps-menu-tile-service';
7
+ import { PsText } from '../ps-text/ps-text';
8
+ export const PsMenuPopupTile = ({ imageUri, monogram, tileColor }) => {
9
+ const theme = usePsMobileTheme();
10
+ const size = PsMenuPopupConfig.tileSize;
11
+ const fill = tileColor === undefined ? theme.primary : tileColor;
12
+ const textColor = tileColor === undefined
13
+ ? theme.primaryContrast
14
+ : PallasoftColorContrastService.textOn(tileColor);
15
+ return (_jsx(View, { style: {
16
+ alignItems: 'center',
17
+ backgroundColor: imageUri === undefined ? fill : theme.white,
18
+ borderRadius: PsMenuPopupConfig.tileRadius,
19
+ height: size,
20
+ justifyContent: 'center',
21
+ overflow: 'hidden',
22
+ width: size,
23
+ }, testID: 'ps-menu-popup-tile', children: imageUri === undefined ? (_jsx(PsText, { style: { color: textColor, fontSize: PsMenuPopupConfig.tileFontSize }, weight: 'semibold', children: PsMenuTileService.letter(monogram === undefined ? '' : monogram) })) : (_jsx(Image, { resizeMode: 'contain', source: { uri: imageUri }, style: {
24
+ height: size - PsMenuPopupConfig.tileLogoPadding * 2,
25
+ width: size - PsMenuPopupConfig.tileLogoPadding * 2,
26
+ } })) }));
27
+ };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PsIconKey } from '@pallasoft/ps-icons';
3
- import { StyleSheet, View } from 'react-native';
3
+ import { Platform, StyleSheet, View } from 'react-native';
4
4
  import { PsPageHeaderConfig } from '../../constants/ps-page-header-config';
5
5
  import { PsSheetConfig } from '../../constants/ps-sheet-config';
6
6
  import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
@@ -20,7 +20,7 @@ export const PsSheetHeader = ({ eyebrow, isLeadingDisabled = false, leadingKind
20
20
  minHeight: PsSheetConfig.headerMinHeight,
21
21
  paddingBottom: PsSheetConfig.headerPaddingBottom,
22
22
  paddingHorizontal: PsSheetConfig.headerPaddingHorizontal,
23
- paddingTop: PsSheetConfig.headerPaddingTop,
23
+ paddingTop: PsSheetService.headerPaddingTop(Platform.OS),
24
24
  }, testID: 'ps-sheet-header', children: [_jsx(View, { style: { alignItems: 'flex-start', minWidth: PsSheetConfig.actionMinWidth }, children: leadingLabel === undefined ? null : (_jsx(View, { style: PsSheetService.leadingIconStyle(leadingKind), children: _jsx(PsIconButton, { iconKey: leadingKind === 'back' ? PsIconKey.ChevronRight : PsIconKey.Close, isDisabled: isLeadingDisabled, label: leadingLabel, onPress: onLeadingPress, testID: 'ps-sheet-leading', variant: 'header' }) })) }), _jsxs(View, { style: { alignItems: 'center', flex: 1 }, children: [_jsx(PsText, { accessibilityRole: 'header', align: 'center', numberOfLines: 2, weight: 'bold', children: title }), eyebrow === undefined ? null : (_jsx(PsText, { align: 'center', numberOfLines: 1, tone: 'muted', variant: 'small', children: eyebrow }))] }), _jsx(View, { style: { alignItems: 'flex-end', minWidth: PsSheetConfig.actionMinWidth }, children: trailing === undefined ? null : trailing.isSubmitting === true ? (_jsx(View, { accessibilityLabel: trailing.label, accessibilityState: { busy: true }, style: {
25
25
  alignItems: 'center',
26
26
  backgroundColor: trailingColor,
@@ -8,4 +8,8 @@ export declare const PsMenuPopupConfig: {
8
8
  readonly remeasureMs: 250;
9
9
  readonly rowHeight: 48;
10
10
  readonly rowPaddingHorizontal: 16;
11
+ readonly tileFontSize: 14;
12
+ readonly tileLogoPadding: 3;
13
+ readonly tileRadius: 8;
14
+ readonly tileSize: 28;
11
15
  };
@@ -8,4 +8,8 @@ export const PsMenuPopupConfig = {
8
8
  remeasureMs: 250,
9
9
  rowHeight: 48,
10
10
  rowPaddingHorizontal: 16,
11
+ tileFontSize: 14,
12
+ tileLogoPadding: 3,
13
+ tileRadius: 8,
14
+ tileSize: 28,
11
15
  };
@@ -1,6 +1,7 @@
1
1
  export declare const PsSheetConfig: {
2
2
  readonly actionMinWidth: 44;
3
3
  readonly actionSpinnerGap: 6;
4
+ readonly androidHeaderPaddingTop: 0;
4
5
  readonly cornerRadius: 20;
5
6
  readonly formSizingMinIos: 18;
6
7
  readonly headerMinHeight: 64;
@@ -1,6 +1,7 @@
1
1
  export const PsSheetConfig = {
2
2
  actionMinWidth: 44,
3
3
  actionSpinnerGap: 6,
4
+ androidHeaderPaddingTop: 0,
4
5
  cornerRadius: 20,
5
6
  formSizingMinIos: 18,
6
7
  headerMinHeight: 64,
@@ -13,5 +13,6 @@ export interface PsMenuItem {
13
13
  monogram?: string;
14
14
  onSelect: () => void;
15
15
  subtitle?: string;
16
+ tileColor?: string;
16
17
  tone?: PsMenuItemTone;
17
18
  }
@@ -3,5 +3,6 @@ export interface PsMenuTileRequest {
3
3
  imageUri?: string;
4
4
  key: string;
5
5
  shape: PsMenuTileShape;
6
+ tileColor?: string;
6
7
  text: string;
7
8
  }
@@ -4,14 +4,14 @@ export class PsMenuTileService {
4
4
  if (!PsActionMenuService.hasAvatar(item))
5
5
  return null;
6
6
  const text = PsMenuTileService.letter(item.monogram === undefined ? item.label : item.monogram);
7
- return PsMenuTileService.request('rounded', item.imageUri, text);
7
+ return PsMenuTileService.request('rounded', item.imageUri, text, item.tileColor);
8
8
  }
9
9
  static headerRequest(header) {
10
10
  if (header === undefined)
11
11
  return null;
12
12
  if (header.imageUri === undefined && header.initials === undefined)
13
13
  return null;
14
- return PsMenuTileService.request('circle', header.imageUri, header.initials === undefined ? '' : header.initials.trim().toUpperCase());
14
+ return PsMenuTileService.request('circle', header.imageUri, header.initials === undefined ? '' : header.initials.trim().toUpperCase(), undefined);
15
15
  }
16
16
  static requests(header, sections) {
17
17
  const all = [
@@ -59,12 +59,18 @@ export class PsMenuTileService {
59
59
  return [item];
60
60
  return [item, ...item.children.flatMap((child) => PsMenuTileService.tree(child))];
61
61
  }
62
- static request(shape, imageUri, text) {
62
+ static request(shape, imageUri, text, tileColor) {
63
63
  return {
64
64
  imageUri,
65
- key: [shape, imageUri === undefined ? '' : imageUri, text].join('|'),
65
+ key: [
66
+ shape,
67
+ imageUri === undefined ? '' : imageUri,
68
+ text,
69
+ tileColor === undefined ? '' : tileColor,
70
+ ].join('|'),
66
71
  shape,
67
72
  text,
73
+ tileColor,
68
74
  };
69
75
  }
70
76
  }
@@ -7,6 +7,7 @@ export declare class PsSheetService {
7
7
  static fittedMaxWidth(windowWidth: number, layoutClass: PsLayoutClass): number | undefined;
8
8
  static supportsFormSizing(iosVersion: string | number): boolean;
9
9
  static handlesInset(os: string): boolean;
10
+ static headerPaddingTop(os: string): number;
10
11
  static bottomPadding(isFloating: boolean, bottomInset: number): number;
11
12
  static fittedHeight(headerHeight: number, bodyHeight: number, windowHeight: number, layoutClass?: PsLayoutClass): number;
12
13
  static raisedHeight(height: number, keyboardHeight: number, windowHeight: number, topInset: number): number;
@@ -20,6 +20,9 @@ export class PsSheetService {
20
20
  static handlesInset(os) {
21
21
  return os === 'ios' || os === 'android';
22
22
  }
23
+ static headerPaddingTop(os) {
24
+ return os === 'android' ? PsSheetConfig.androidHeaderPaddingTop : PsSheetConfig.headerPaddingTop;
25
+ }
23
26
  static bottomPadding(isFloating, bottomInset) {
24
27
  return PsLayoutConfig.sheetGutter + (isFloating ? 0 : bottomInset);
25
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-mobile-components",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "private": false,
5
5
  "description": "Native iOS (SwiftUI) and Android (Jetpack Compose) Ps components for Pallasoft mobile apps",
6
6
  "main": "./dist/index.js",