@pallasoft/ps-mobile-components 1.1.0 → 1.2.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.
@@ -0,0 +1,2 @@
1
+ export * from './ps-count-badge';
2
+ export * from './ps-count-badge-props';
@@ -0,0 +1,2 @@
1
+ export * from './ps-count-badge';
2
+ export * from './ps-count-badge-props';
@@ -0,0 +1,5 @@
1
+ export interface PsCountBadgeProps {
2
+ count: number;
3
+ isAlert?: boolean;
4
+ label?: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsCountBadgeProps } from './ps-count-badge-props';
2
+ export declare const PsCountBadge: ({ count, isAlert, label }: PsCountBadgeProps) => import("react").JSX.Element | null;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import { PsCountBadgeConfig } from '../../constants/ps-count-badge-config';
4
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
5
+ import { PsCountBadgeService } from '../../services/ps-count-badge-service';
6
+ import { PsText } from '../ps-text/ps-text';
7
+ export const PsCountBadge = ({ count, isAlert = false, label }) => {
8
+ const theme = usePsMobileTheme();
9
+ if (count <= 0)
10
+ return null;
11
+ return (_jsx(View, { accessibilityLabel: label, style: {
12
+ alignItems: 'center',
13
+ alignSelf: 'center',
14
+ backgroundColor: isAlert ? theme.danger : theme.primary,
15
+ borderRadius: PsCountBadgeConfig.height / 2,
16
+ height: PsCountBadgeConfig.height,
17
+ justifyContent: 'center',
18
+ minWidth: PsCountBadgeConfig.height,
19
+ paddingHorizontal: PsCountBadgeConfig.paddingHorizontal,
20
+ }, testID: 'ps-count-badge', children: _jsx(PsText, { style: {
21
+ color: isAlert ? theme.white : theme.primaryContrast,
22
+ fontSize: PsCountBadgeConfig.fontSize,
23
+ lineHeight: PsCountBadgeConfig.height,
24
+ }, weight: 'bold', children: PsCountBadgeService.text(count) }) }));
25
+ };
@@ -8,3 +8,5 @@ export * from './ps-list-select-row';
8
8
  export * from './ps-list-select-row-props';
9
9
  export * from './ps-list-person-row';
10
10
  export * from './ps-list-person-row-props';
11
+ export * from './ps-list-link-row';
12
+ export * from './ps-list-link-row-props';
@@ -8,3 +8,5 @@ export * from './ps-list-select-row';
8
8
  export * from './ps-list-select-row-props';
9
9
  export * from './ps-list-person-row';
10
10
  export * from './ps-list-person-row-props';
11
+ export * from './ps-list-link-row';
12
+ export * from './ps-list-link-row-props';
@@ -0,0 +1,11 @@
1
+ import type { PsMobileIconKey } from '../../constants/ps-mobile-icon-key';
2
+ export interface PsListLinkRowProps {
3
+ count?: number;
4
+ countLabel?: string;
5
+ iconKey: PsMobileIconKey;
6
+ isAlert?: boolean;
7
+ isLoading?: boolean;
8
+ onPress: () => void;
9
+ subtitle?: string;
10
+ title: string;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const PsListLinkRowSkeleton: () => import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { View } from 'react-native';
3
+ import { PsLayoutConfig } from '../../constants/ps-layout-config';
4
+ import { PsListConfig } from '../../constants/ps-list-config';
5
+ import { PsSkeletonShape } from '../../constants/ps-skeleton-shape';
6
+ import { PsSkeleton } from '../ps-skeleton/ps-skeleton';
7
+ export const PsListLinkRowSkeleton = () => (_jsxs(View, { style: {
8
+ alignItems: 'center',
9
+ flexDirection: 'row',
10
+ gap: PsLayoutConfig.headerSlotGap,
11
+ minHeight: PsLayoutConfig.linkRowMinHeight,
12
+ paddingHorizontal: PsLayoutConfig.gutter,
13
+ paddingVertical: PsLayoutConfig.rowPaddingVertical,
14
+ }, testID: 'ps-list-link-row-skeleton', children: [_jsx(PsSkeleton, { height: PsLayoutConfig.linkRowIconBox, radius: PsLayoutConfig.linkRowIconBoxRadius, width: PsLayoutConfig.linkRowIconBox }), _jsxs(View, { style: { flex: 1, gap: PsLayoutConfig.rowLineGap * 3 }, children: [_jsx(PsSkeleton, { shape: PsSkeletonShape.Line, width: PsListConfig.skeletonTitleWidth }), _jsx(PsSkeleton, { shape: PsSkeletonShape.Line, width: PsListConfig.skeletonSubtitleWidth })] })] }));
@@ -0,0 +1,2 @@
1
+ import type { PsListLinkRowProps } from './ps-list-link-row-props';
2
+ export declare const PsListLinkRow: ({ count, countLabel, iconKey, isAlert, isLoading, onPress, subtitle, title, }: PsListLinkRowProps) => import("react").JSX.Element;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsIconKey } from '@pallasoft/ps-icons';
3
+ import { Pressable, View } from 'react-native';
4
+ import { PsLayoutConfig } from '../../constants/ps-layout-config';
5
+ import { PsMobileControlConfig } from '../../constants/ps-mobile-control-config';
6
+ import { usePsHaptics } from '../../hooks/use-ps-haptics';
7
+ import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
8
+ import { PsMobileColorService } from '../../services/ps-mobile-color-service';
9
+ import { PsCountBadge } from '../ps-count-badge/ps-count-badge';
10
+ import { PsIcon } from '../ps-icon/ps-icon';
11
+ import { PsText } from '../ps-text/ps-text';
12
+ import { PsListLinkRowSkeleton } from './ps-list-link-row-skeleton';
13
+ export const PsListLinkRow = ({ count, countLabel, iconKey, isAlert = false, isLoading = false, onPress, subtitle, title, }) => {
14
+ const theme = usePsMobileTheme();
15
+ const haptic = usePsHaptics();
16
+ if (isLoading)
17
+ return _jsx(PsListLinkRowSkeleton, {});
18
+ const hasCount = count !== undefined && count > 0;
19
+ return (_jsxs(Pressable, { accessibilityHint: countLabel, accessibilityRole: 'button', onPress: () => {
20
+ haptic('light');
21
+ onPress();
22
+ }, style: ({ pressed }) => ({
23
+ alignItems: 'center',
24
+ backgroundColor: pressed
25
+ ? PsMobileColorService.withAlpha(theme.ink, PsMobileControlConfig.pressedAlpha)
26
+ : theme.surface,
27
+ flexDirection: 'row',
28
+ gap: PsLayoutConfig.headerSlotGap,
29
+ minHeight: PsLayoutConfig.linkRowMinHeight,
30
+ paddingHorizontal: PsLayoutConfig.gutter,
31
+ paddingVertical: PsLayoutConfig.rowPaddingVertical,
32
+ }), testID: 'ps-list-link-row', children: [_jsx(View, { style: {
33
+ alignItems: 'center',
34
+ backgroundColor: theme.primarySoft,
35
+ borderRadius: PsLayoutConfig.linkRowIconBoxRadius,
36
+ height: PsLayoutConfig.linkRowIconBox,
37
+ justifyContent: 'center',
38
+ width: PsLayoutConfig.linkRowIconBox,
39
+ }, children: _jsx(PsIcon, { color: theme.primary, iconKey: iconKey, size: PsLayoutConfig.linkRowIconSize }) }), _jsxs(View, { style: { flex: 1, gap: PsLayoutConfig.rowLineGap }, children: [_jsx(PsText, { numberOfLines: 1, weight: 'semibold', children: title }), subtitle === undefined ? null : (_jsx(PsText, { numberOfLines: 1, tone: 'muted', variant: 'small', children: subtitle }))] }), hasCount ? _jsx(PsCountBadge, { count: count, isAlert: isAlert, label: countLabel }) : null, _jsx(PsIcon, { color: theme.muted, iconKey: PsIconKey.ChevronRight, size: PsMobileControlConfig.iconSizeInline })] }));
40
+ };
@@ -0,0 +1,6 @@
1
+ export declare const PsCountBadgeConfig: {
2
+ readonly fontSize: 13;
3
+ readonly height: 24;
4
+ readonly maxCount: 99;
5
+ readonly paddingHorizontal: 7;
6
+ };
@@ -0,0 +1,6 @@
1
+ export const PsCountBadgeConfig = {
2
+ fontSize: 13,
3
+ height: 24,
4
+ maxCount: 99,
5
+ paddingHorizontal: 7,
6
+ };
@@ -6,6 +6,10 @@ export declare const PsLayoutConfig: {
6
6
  readonly headerGap: 4;
7
7
  readonly headerSlotGap: 12;
8
8
  readonly hitSlop: 8;
9
+ readonly linkRowIconBox: 44;
10
+ readonly linkRowIconBoxRadius: 12;
11
+ readonly linkRowIconSize: 22;
12
+ readonly linkRowMinHeight: 72;
9
13
  readonly listGap: 24;
10
14
  readonly listSectionGap: 8;
11
15
  readonly longPressDelayMs: 350;
@@ -6,6 +6,10 @@ export const PsLayoutConfig = {
6
6
  headerGap: 4,
7
7
  headerSlotGap: 12,
8
8
  hitSlop: 8,
9
+ linkRowIconBox: 44,
10
+ linkRowIconBoxRadius: 12,
11
+ linkRowIconSize: 22,
12
+ linkRowMinHeight: 72,
9
13
  listGap: 24,
10
14
  listSectionGap: 8,
11
15
  longPressDelayMs: 350,
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export * from './components/ps-mobile-theme-provider';
25
25
  export * from './components/ps-page-backdrop';
26
26
  export * from './components/ps-pallasoft-sign-in-button';
27
27
  export * from './components/ps-color-swatches';
28
+ export * from './components/ps-count-badge';
28
29
  export * from './components/ps-segmented-control';
29
30
  export * from './components/ps-tabs';
30
31
  export * from './components/ps-select';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export * from './components/ps-mobile-theme-provider';
25
25
  export * from './components/ps-page-backdrop';
26
26
  export * from './components/ps-pallasoft-sign-in-button';
27
27
  export * from './components/ps-color-swatches';
28
+ export * from './components/ps-count-badge';
28
29
  export * from './components/ps-segmented-control';
29
30
  export * from './components/ps-tabs';
30
31
  export * from './components/ps-select';
@@ -0,0 +1,3 @@
1
+ export declare class PsCountBadgeService {
2
+ static text(count: number): string;
3
+ }
@@ -0,0 +1,6 @@
1
+ import { PsCountBadgeConfig } from '../constants/ps-count-badge-config';
2
+ export class PsCountBadgeService {
3
+ static text(count) {
4
+ return count > PsCountBadgeConfig.maxCount ? `${PsCountBadgeConfig.maxCount}+` : String(count);
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-mobile-components",
3
- "version": "1.1.0",
3
+ "version": "1.2.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",