@pallasoft/ps-core-components 2.19.0 → 2.20.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.
Files changed (55) hide show
  1. package/dist/components/ps-capped-list.d.ts +9 -0
  2. package/dist/components/ps-capped-list.js +14 -0
  3. package/dist/components/ps-detail-list.d.ts +6 -0
  4. package/dist/components/ps-detail-list.js +11 -0
  5. package/dist/components/ps-form-footer.d.ts +10 -0
  6. package/dist/components/ps-form-footer.js +7 -0
  7. package/dist/components/ps-section-panel.d.ts +3 -1
  8. package/dist/components/ps-section-panel.js +1 -1
  9. package/dist/components/ps-start-highlight.d.ts +9 -0
  10. package/dist/components/ps-start-highlight.js +6 -0
  11. package/dist/components/ps-start-panel.d.ts +11 -0
  12. package/dist/components/ps-start-panel.js +9 -0
  13. package/dist/components/ps-start-preview.d.ts +8 -0
  14. package/dist/components/ps-start-preview.js +6 -0
  15. package/dist/components/ps-step-list.d.ts +5 -0
  16. package/dist/components/ps-step-list.js +10 -0
  17. package/dist/components/ps-summary-bar.d.ts +5 -0
  18. package/dist/components/ps-summary-bar.js +8 -0
  19. package/dist/constants/ps-capped-list-config.d.ts +3 -0
  20. package/dist/constants/ps-capped-list-config.js +6 -0
  21. package/dist/constants/ps-step-marker-class-names.d.ts +2 -0
  22. package/dist/constants/ps-step-marker-class-names.js +9 -0
  23. package/dist/constants/ps-step-state.d.ts +6 -0
  24. package/dist/constants/ps-step-state.js +8 -0
  25. package/dist/esm/components/ps-capped-list.js +10 -0
  26. package/dist/esm/components/ps-detail-list.js +7 -0
  27. package/dist/esm/components/ps-form-footer.js +3 -0
  28. package/dist/esm/components/ps-section-panel.js +1 -1
  29. package/dist/esm/components/ps-start-highlight.js +2 -0
  30. package/dist/esm/components/ps-start-panel.js +5 -0
  31. package/dist/esm/components/ps-start-preview.js +2 -0
  32. package/dist/esm/components/ps-step-list.js +6 -0
  33. package/dist/esm/components/ps-summary-bar.js +4 -0
  34. package/dist/esm/constants/ps-capped-list-config.js +3 -0
  35. package/dist/esm/constants/ps-step-marker-class-names.js +6 -0
  36. package/dist/esm/constants/ps-step-state.js +5 -0
  37. package/dist/esm/hooks/use-ps-dismissable.js +25 -0
  38. package/dist/esm/index.js +14 -0
  39. package/dist/esm/models/ps-detail-list-item.js +1 -0
  40. package/dist/esm/models/ps-start-panel-step.js +1 -0
  41. package/dist/esm/models/ps-step-list-item.js +1 -0
  42. package/dist/esm/models/ps-summary-item.js +1 -0
  43. package/dist/hooks/use-ps-dismissable.d.ts +2 -0
  44. package/dist/hooks/use-ps-dismissable.js +29 -0
  45. package/dist/index.d.ts +14 -0
  46. package/dist/index.js +14 -0
  47. package/dist/models/ps-detail-list-item.d.ts +5 -0
  48. package/dist/models/ps-detail-list-item.js +2 -0
  49. package/dist/models/ps-start-panel-step.d.ts +5 -0
  50. package/dist/models/ps-start-panel-step.js +2 -0
  51. package/dist/models/ps-step-list-item.d.ts +10 -0
  52. package/dist/models/ps-step-list-item.js +2 -0
  53. package/dist/models/ps-summary-item.d.ts +6 -0
  54. package/dist/models/ps-summary-item.js +2 -0
  55. package/package.json +6 -3
@@ -0,0 +1,9 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface PsCappedListProps<Item> {
3
+ getKey: (item: Item) => string;
4
+ items: Item[];
5
+ renderItem: (item: Item) => ReactNode;
6
+ showAllLabel: string;
7
+ visibleRows?: number;
8
+ }
9
+ export declare const PsCappedList: <Item>({ getKey, items, renderItem, showAllLabel, visibleRows, }: PsCappedListProps<Item>) => import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsCappedList = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ps_capped_list_config_1 = require("../constants/ps-capped-list-config");
7
+ const ps_button_1 = require("./ps-button");
8
+ const PsCappedList = ({ getKey, items, renderItem, showAllLabel, visibleRows = ps_capped_list_config_1.PsCappedListConfig.defaultVisibleRows, }) => {
9
+ const [isExpanded, setIsExpanded] = (0, react_1.useState)(false);
10
+ const isCapped = !isExpanded && items.length > visibleRows;
11
+ const shown = isCapped ? items.slice(0, visibleRows) : items;
12
+ return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2', children: [(0, jsx_runtime_1.jsx)("ul", { className: '-my-2 flex flex-col divide-y divide-line', children: shown.map((item) => ((0, jsx_runtime_1.jsx)("li", { children: renderItem(item) }, getKey(item)))) }), isCapped ? ((0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { className: 'self-start', onClick: () => setIsExpanded(true), size: 'compact', variant: 'ghost', children: showAllLabel })) : null] }));
13
+ };
14
+ exports.PsCappedList = PsCappedList;
@@ -0,0 +1,6 @@
1
+ import type { PsDetailListItem } from '../models/ps-detail-list-item';
2
+ export interface PsDetailListProps {
3
+ emptyLabel: string;
4
+ items: PsDetailListItem[];
5
+ }
6
+ export declare const PsDetailList: ({ emptyLabel, items }: PsDetailListProps) => import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsDetailList = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const PsDetailList = ({ emptyLabel, items }) => {
6
+ const filled = items.filter((item) => item.value !== null && item.value !== '');
7
+ if (filled.length === 0)
8
+ return (0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-ink/50', children: emptyLabel });
9
+ return ((0, jsx_runtime_1.jsx)("dl", { className: 'grid grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-x-6 gap-y-3', children: filled.map((item) => ((0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0', children: [(0, jsx_runtime_1.jsx)("dt", { className: 'text-xs font-medium text-ink/50', children: item.label }), (0, jsx_runtime_1.jsx)("dd", { className: 'mt-0.5 text-sm break-words whitespace-pre-line text-ink', children: item.value })] }, item.label))) }));
10
+ };
11
+ exports.PsDetailList = PsDetailList;
@@ -0,0 +1,10 @@
1
+ export interface PsFormFooterProps {
2
+ isSaving: boolean;
3
+ isSecondaryVisible?: boolean;
4
+ isSubmitDisabled: boolean;
5
+ onSecondary: () => void;
6
+ savingLabel: string;
7
+ secondaryLabel: string;
8
+ submitLabel: string;
9
+ }
10
+ export declare const PsFormFooter: ({ isSaving, isSecondaryVisible, isSubmitDisabled, onSecondary, savingLabel, secondaryLabel, submitLabel, }: PsFormFooterProps) => import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsFormFooter = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_button_1 = require("./ps-button");
6
+ const PsFormFooter = ({ isSaving, isSecondaryVisible = true, isSubmitDisabled, onSecondary, savingLabel, secondaryLabel, submitLabel, }) => ((0, jsx_runtime_1.jsxs)("div", { className: 'sticky bottom-0 z-docked -mx-4 flex justify-end gap-2 border-t border-line bg-surface/90 px-4 py-3 backdrop-blur sm:bottom-4 sm:mx-0 sm:border-0 sm:bg-transparent sm:px-0 sm:py-0 sm:backdrop-blur-none', children: [isSecondaryVisible ? ((0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { disabled: isSaving, onClick: onSecondary, type: 'button', variant: 'surface', children: secondaryLabel })) : null, (0, jsx_runtime_1.jsx)(ps_button_1.PsButton, { disabled: isSubmitDisabled || isSaving, isLoading: isSaving, loadingLabel: savingLabel, type: 'submit', variant: 'primary', children: submitLabel })] }));
7
+ exports.PsFormFooter = PsFormFooter;
@@ -3,6 +3,8 @@ export interface PsSectionPanelProps {
3
3
  actions?: ReactNode;
4
4
  children: ReactNode;
5
5
  className?: string;
6
+ description?: string;
6
7
  title?: string;
8
+ variant?: 'bordered' | 'card' | 'plain';
7
9
  }
8
- export declare const PsSectionPanel: ({ actions, children, className, title }: PsSectionPanelProps) => import("react").JSX.Element;
10
+ export declare const PsSectionPanel: ({ actions, children, className, description, title, variant, }: PsSectionPanelProps) => import("react").JSX.Element;
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsSectionPanel = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
6
- const PsSectionPanel = ({ actions, children, className, title }) => ((0, jsx_runtime_1.jsxs)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-section', className), children: [title || actions ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex min-h-10 flex-wrap items-center justify-between gap-3', children: [title ? (0, jsx_runtime_1.jsx)("h2", { className: 'text-base font-semibold text-ink', children: title }) : (0, jsx_runtime_1.jsx)("span", {}), actions ? (0, jsx_runtime_1.jsx)("div", { className: 'flex flex-wrap items-center gap-2', children: actions }) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: 'flex min-w-0 flex-col gap-4', children: children })] }));
6
+ const PsSectionPanel = ({ actions, children, className, description, title, variant = 'plain', }) => variant === 'card' ? ((0, jsx_runtime_1.jsxs)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('rounded-ps-panel border border-line bg-surface p-5', className), children: [title ? (0, jsx_runtime_1.jsx)("h2", { className: 'mb-3 text-sm font-semibold text-ink', children: title }) : null, children] })) : variant === 'bordered' ? ((0, jsx_runtime_1.jsxs)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('rounded-ps-panel border border-line bg-surface', className), children: [title || actions ? ((0, jsx_runtime_1.jsxs)("header", { className: 'flex min-h-14 items-center justify-between gap-3 border-b border-line px-5 py-3', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-col', children: [title ? (0, jsx_runtime_1.jsx)("h2", { className: 'text-base font-semibold text-ink', children: title }) : null, description ? (0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-ink/60', children: description }) : null] }), actions ? (0, jsx_runtime_1.jsx)("div", { className: 'flex items-center gap-2', children: actions }) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: 'flex flex-col gap-4 p-5', children: children })] })) : ((0, jsx_runtime_1.jsxs)("section", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-section', className), children: [title || actions ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex min-h-10 flex-wrap items-center justify-between gap-3', children: [title ? (0, jsx_runtime_1.jsx)("h2", { className: 'text-base font-semibold text-ink', children: title }) : (0, jsx_runtime_1.jsx)("span", {}), actions ? (0, jsx_runtime_1.jsx)("div", { className: 'flex flex-wrap items-center gap-2', children: actions }) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: 'flex min-w-0 flex-col gap-4', children: children })] }));
7
7
  exports.PsSectionPanel = PsSectionPanel;
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsStartHighlightProps {
3
+ description: string;
4
+ icon: ReactNode;
5
+ label: string;
6
+ onClick: () => void;
7
+ title?: string;
8
+ }
9
+ export declare const PsStartHighlight: ({ description, icon, label, onClick, title, }: PsStartHighlightProps) => import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStartHighlight = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const PsStartHighlight = ({ description, icon, label, onClick, title, }) => ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2', children: [title === undefined ? null : ((0, jsx_runtime_1.jsx)("span", { className: 'text-xs font-semibold uppercase tracking-wide text-ink/40', children: title })), (0, jsx_runtime_1.jsxs)("button", { className: 'ps-focus-ring group flex items-center gap-4 rounded-2xl border border-primary/40 bg-gradient-to-br from-primary/20 via-primary/10 to-transparent p-4 text-left transition-colors hover:border-primary/70', onClick: onClick, type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { className: 'flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-primary text-white', children: icon }), (0, jsx_runtime_1.jsxs)("span", { className: 'flex min-w-0 flex-col gap-0.5', children: [(0, jsx_runtime_1.jsx)("span", { className: 'truncate text-base font-bold text-ink', title: label, children: label }), (0, jsx_runtime_1.jsx)("span", { className: 'text-sm leading-5 text-ink/60', children: description })] })] })] }));
6
+ exports.PsStartHighlight = PsStartHighlight;
@@ -0,0 +1,11 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PsStartPanelStep } from '../models/ps-start-panel-step';
3
+ export interface PsStartPanelProps {
4
+ actions?: ReactNode;
5
+ description: string;
6
+ highlight?: ReactNode;
7
+ preview?: ReactNode;
8
+ steps?: readonly PsStartPanelStep[];
9
+ title: string;
10
+ }
11
+ export declare const PsStartPanel: ({ actions, description, highlight, preview, steps, title, }: PsStartPanelProps) => import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStartPanel = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_card_1 = require("./ps-card");
6
+ const PsStartPanel = ({ actions, description, highlight, preview, steps, title, }) => ((0, jsx_runtime_1.jsx)(ps_card_1.PsCard, { density: 'spacious', children: (0, jsx_runtime_1.jsxs)("div", { className: preview
7
+ ? 'grid items-center gap-10 xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]'
8
+ : 'grid items-center gap-10', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-7', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-3', children: [(0, jsx_runtime_1.jsx)("h2", { className: 'text-3xl font-extrabold leading-tight text-ink', children: title }), (0, jsx_runtime_1.jsx)("p", { className: 'max-w-md text-sm leading-6 text-ink/60', children: description })] }), highlight, steps === undefined ? null : ((0, jsx_runtime_1.jsx)("ol", { className: 'flex flex-col gap-4', children: steps.map((step, index) => ((0, jsx_runtime_1.jsxs)("li", { className: 'flex items-start gap-4', children: [(0, jsx_runtime_1.jsx)("span", { className: 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-line/60 text-sm font-bold text-ink/70', children: index + 1 }), (0, jsx_runtime_1.jsxs)("span", { className: 'flex min-w-0 flex-col gap-0.5', children: [(0, jsx_runtime_1.jsx)("span", { className: 'text-sm font-bold text-ink', children: step.title }), (0, jsx_runtime_1.jsx)("span", { className: 'text-sm leading-5 text-ink/55', children: step.description })] })] }, step.id))) })), actions] }), preview === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: 'relative hidden xl:block', children: preview })] }) }));
9
+ exports.PsStartPanel = PsStartPanel;
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsStartPreviewProps {
3
+ badge?: string;
4
+ children: ReactNode;
5
+ note?: string;
6
+ title: string;
7
+ }
8
+ export declare const PsStartPreview: ({ badge, children, note, title }: PsStartPreviewProps) => import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStartPreview = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const PsStartPreview = ({ badge, children, note, title }) => ((0, jsx_runtime_1.jsxs)("div", { className: 'rounded-2xl border border-line/60 bg-surface/60 p-5', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'mb-4 flex items-center justify-between', children: [(0, jsx_runtime_1.jsx)("span", { className: 'text-sm font-bold text-ink', children: title }), badge === undefined ? null : ((0, jsx_runtime_1.jsx)("span", { className: 'rounded-full border border-warning/40 px-2.5 py-1 text-xs font-semibold text-warning', children: badge }))] }), (0, jsx_runtime_1.jsx)("ul", { className: 'flex flex-col', children: children }), note === undefined ? null : ((0, jsx_runtime_1.jsx)("p", { className: 'mt-4 border-t border-line/40 pt-4 text-xs leading-5 text-ink/45', children: note }))] }));
6
+ exports.PsStartPreview = PsStartPreview;
@@ -0,0 +1,5 @@
1
+ import type { PsStepListItem } from '../models/ps-step-list-item';
2
+ export interface PsStepListProps {
3
+ steps: PsStepListItem[];
4
+ }
5
+ export declare const PsStepList: ({ steps }: PsStepListProps) => import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStepList = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ps_step_marker_class_names_1 = require("../constants/ps-step-marker-class-names");
6
+ const ps_step_state_1 = require("../constants/ps-step-state");
7
+ const PsStepList = ({ steps }) => ((0, jsx_runtime_1.jsx)("ol", { className: 'flex flex-col rounded-ps-panel border border-line bg-surface', children: steps.map((step, index) => ((0, jsx_runtime_1.jsxs)("li", { "aria-current": step.state === ps_step_state_1.PsStepState.current ? 'step' : undefined, className: 'relative flex gap-4 border-b border-line/70 px-5 py-4 last:border-0', children: [(0, jsx_runtime_1.jsx)("span", { className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold ${ps_step_marker_class_names_1.PsStepMarkerClassNames[step.state]}`, children: step.state === ps_step_state_1.PsStepState.done ? (0, jsx_runtime_1.jsx)("span", { "aria-hidden": 'true', children: "\u2713" }) : index + 1 }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-1 flex-col gap-3', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0', children: [(0, jsx_runtime_1.jsx)("h3", { className: step.state === ps_step_state_1.PsStepState.upcoming
8
+ ? 'text-sm font-semibold text-ink/50'
9
+ : 'text-sm font-semibold text-ink', children: step.title }), (0, jsx_runtime_1.jsx)("p", { className: 'mt-0.5 text-sm text-ink/60', children: step.description })] }), step.actions ? ((0, jsx_runtime_1.jsx)("div", { className: 'flex shrink-0 flex-wrap items-center gap-2', children: step.actions })) : null] }), step.content ? (0, jsx_runtime_1.jsx)("div", { className: 'min-w-0', children: step.content }) : null] })] }, step.id))) }));
10
+ exports.PsStepList = PsStepList;
@@ -0,0 +1,5 @@
1
+ import type { PsSummaryItem } from '../models/ps-summary-item';
2
+ export interface PsSummaryBarProps {
3
+ items: PsSummaryItem[];
4
+ }
5
+ export declare const PsSummaryBar: ({ items }: PsSummaryBarProps) => import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsSummaryBar = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const PsSummaryBar = ({ items }) => ((0, jsx_runtime_1.jsx)("dl", { className: 'grid grid-cols-2 gap-x-6 gap-y-4 rounded-ps-panel border border-line bg-surface px-5 py-4 sm:flex sm:flex-wrap sm:items-center sm:gap-x-10', children: items.map((item) => ((0, jsx_runtime_1.jsxs)("div", { className: 'flex min-w-0 flex-col gap-1', children: [(0, jsx_runtime_1.jsx)("dt", { className: 'text-xs font-medium uppercase tracking-wide text-ink/50', children: item.label }), (0, jsx_runtime_1.jsx)("dd", { className: item.emphasis
6
+ ? 'truncate text-xl font-semibold tabular-nums text-ink'
7
+ : 'truncate text-sm font-medium text-ink', children: item.value })] }, item.label))) }));
8
+ exports.PsSummaryBar = PsSummaryBar;
@@ -0,0 +1,3 @@
1
+ export declare const PsCappedListConfig: {
2
+ readonly defaultVisibleRows: 5;
3
+ };
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsCappedListConfig = void 0;
4
+ exports.PsCappedListConfig = {
5
+ defaultVisibleRows: 5,
6
+ };
@@ -0,0 +1,2 @@
1
+ import { PsStepState } from './ps-step-state';
2
+ export declare const PsStepMarkerClassNames: Record<PsStepState, string>;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStepMarkerClassNames = void 0;
4
+ const ps_step_state_1 = require("./ps-step-state");
5
+ exports.PsStepMarkerClassNames = {
6
+ [ps_step_state_1.PsStepState.done]: 'border-success bg-success text-white',
7
+ [ps_step_state_1.PsStepState.current]: 'border-primary bg-primary/10 text-primary',
8
+ [ps_step_state_1.PsStepState.upcoming]: 'border-line bg-surface text-ink/40',
9
+ };
@@ -0,0 +1,6 @@
1
+ export declare const PsStepState: {
2
+ readonly current: "current";
3
+ readonly done: "done";
4
+ readonly upcoming: "upcoming";
5
+ };
6
+ export type PsStepState = (typeof PsStepState)[keyof typeof PsStepState];
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsStepState = void 0;
4
+ exports.PsStepState = {
5
+ current: 'current',
6
+ done: 'done',
7
+ upcoming: 'upcoming',
8
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { PsCappedListConfig } from '../constants/ps-capped-list-config';
4
+ import { PsButton } from './ps-button';
5
+ export const PsCappedList = ({ getKey, items, renderItem, showAllLabel, visibleRows = PsCappedListConfig.defaultVisibleRows, }) => {
6
+ const [isExpanded, setIsExpanded] = useState(false);
7
+ const isCapped = !isExpanded && items.length > visibleRows;
8
+ const shown = isCapped ? items.slice(0, visibleRows) : items;
9
+ return (_jsxs("div", { className: 'flex flex-col gap-2', children: [_jsx("ul", { className: '-my-2 flex flex-col divide-y divide-line', children: shown.map((item) => (_jsx("li", { children: renderItem(item) }, getKey(item)))) }), isCapped ? (_jsx(PsButton, { className: 'self-start', onClick: () => setIsExpanded(true), size: 'compact', variant: 'ghost', children: showAllLabel })) : null] }));
10
+ };
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const PsDetailList = ({ emptyLabel, items }) => {
3
+ const filled = items.filter((item) => item.value !== null && item.value !== '');
4
+ if (filled.length === 0)
5
+ return _jsx("p", { className: 'text-sm text-ink/50', children: emptyLabel });
6
+ return (_jsx("dl", { className: 'grid grid-cols-[repeat(auto-fill,minmax(11rem,1fr))] gap-x-6 gap-y-3', children: filled.map((item) => (_jsxs("div", { className: 'min-w-0', children: [_jsx("dt", { className: 'text-xs font-medium text-ink/50', children: item.label }), _jsx("dd", { className: 'mt-0.5 text-sm break-words whitespace-pre-line text-ink', children: item.value })] }, item.label))) }));
7
+ };
@@ -0,0 +1,3 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsButton } from './ps-button';
3
+ export const PsFormFooter = ({ isSaving, isSecondaryVisible = true, isSubmitDisabled, onSecondary, savingLabel, secondaryLabel, submitLabel, }) => (_jsxs("div", { className: 'sticky bottom-0 z-docked -mx-4 flex justify-end gap-2 border-t border-line bg-surface/90 px-4 py-3 backdrop-blur sm:bottom-4 sm:mx-0 sm:border-0 sm:bg-transparent sm:px-0 sm:py-0 sm:backdrop-blur-none', children: [isSecondaryVisible ? (_jsx(PsButton, { disabled: isSaving, onClick: onSecondary, type: 'button', variant: 'surface', children: secondaryLabel })) : null, _jsx(PsButton, { disabled: isSubmitDisabled || isSaving, isLoading: isSaving, loadingLabel: savingLabel, type: 'submit', variant: 'primary', children: submitLabel })] }));
@@ -1,3 +1,3 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- export const PsSectionPanel = ({ actions, children, className, title }) => (_jsxs("section", { className: PallasoftClassNameService.merge('ps-section', className), children: [title || actions ? (_jsxs("div", { className: 'flex min-h-10 flex-wrap items-center justify-between gap-3', children: [title ? _jsx("h2", { className: 'text-base font-semibold text-ink', children: title }) : _jsx("span", {}), actions ? _jsx("div", { className: 'flex flex-wrap items-center gap-2', children: actions }) : null] })) : null, _jsx("div", { className: 'flex min-w-0 flex-col gap-4', children: children })] }));
3
+ export const PsSectionPanel = ({ actions, children, className, description, title, variant = 'plain', }) => variant === 'card' ? (_jsxs("section", { className: PallasoftClassNameService.merge('rounded-ps-panel border border-line bg-surface p-5', className), children: [title ? _jsx("h2", { className: 'mb-3 text-sm font-semibold text-ink', children: title }) : null, children] })) : variant === 'bordered' ? (_jsxs("section", { className: PallasoftClassNameService.merge('rounded-ps-panel border border-line bg-surface', className), children: [title || actions ? (_jsxs("header", { className: 'flex min-h-14 items-center justify-between gap-3 border-b border-line px-5 py-3', children: [_jsxs("div", { className: 'flex min-w-0 flex-col', children: [title ? _jsx("h2", { className: 'text-base font-semibold text-ink', children: title }) : null, description ? _jsx("p", { className: 'text-sm text-ink/60', children: description }) : null] }), actions ? _jsx("div", { className: 'flex items-center gap-2', children: actions }) : null] })) : null, _jsx("div", { className: 'flex flex-col gap-4 p-5', children: children })] })) : (_jsxs("section", { className: PallasoftClassNameService.merge('ps-section', className), children: [title || actions ? (_jsxs("div", { className: 'flex min-h-10 flex-wrap items-center justify-between gap-3', children: [title ? _jsx("h2", { className: 'text-base font-semibold text-ink', children: title }) : _jsx("span", {}), actions ? _jsx("div", { className: 'flex flex-wrap items-center gap-2', children: actions }) : null] })) : null, _jsx("div", { className: 'flex min-w-0 flex-col gap-4', children: children })] }));
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const PsStartHighlight = ({ description, icon, label, onClick, title, }) => (_jsxs("div", { className: 'flex flex-col gap-2', children: [title === undefined ? null : (_jsx("span", { className: 'text-xs font-semibold uppercase tracking-wide text-ink/40', children: title })), _jsxs("button", { className: 'ps-focus-ring group flex items-center gap-4 rounded-2xl border border-primary/40 bg-gradient-to-br from-primary/20 via-primary/10 to-transparent p-4 text-left transition-colors hover:border-primary/70', onClick: onClick, type: 'button', children: [_jsx("span", { className: 'flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-primary text-white', children: icon }), _jsxs("span", { className: 'flex min-w-0 flex-col gap-0.5', children: [_jsx("span", { className: 'truncate text-base font-bold text-ink', title: label, children: label }), _jsx("span", { className: 'text-sm leading-5 text-ink/60', children: description })] })] })] }));
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsCard } from './ps-card';
3
+ export const PsStartPanel = ({ actions, description, highlight, preview, steps, title, }) => (_jsx(PsCard, { density: 'spacious', children: _jsxs("div", { className: preview
4
+ ? 'grid items-center gap-10 xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]'
5
+ : 'grid items-center gap-10', children: [_jsxs("div", { className: 'flex flex-col gap-7', children: [_jsxs("div", { className: 'flex flex-col gap-3', children: [_jsx("h2", { className: 'text-3xl font-extrabold leading-tight text-ink', children: title }), _jsx("p", { className: 'max-w-md text-sm leading-6 text-ink/60', children: description })] }), highlight, steps === undefined ? null : (_jsx("ol", { className: 'flex flex-col gap-4', children: steps.map((step, index) => (_jsxs("li", { className: 'flex items-start gap-4', children: [_jsx("span", { className: 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-line/60 text-sm font-bold text-ink/70', children: index + 1 }), _jsxs("span", { className: 'flex min-w-0 flex-col gap-0.5', children: [_jsx("span", { className: 'text-sm font-bold text-ink', children: step.title }), _jsx("span", { className: 'text-sm leading-5 text-ink/55', children: step.description })] })] }, step.id))) })), actions] }), preview === undefined ? null : _jsx("div", { className: 'relative hidden xl:block', children: preview })] }) }));
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const PsStartPreview = ({ badge, children, note, title }) => (_jsxs("div", { className: 'rounded-2xl border border-line/60 bg-surface/60 p-5', children: [_jsxs("div", { className: 'mb-4 flex items-center justify-between', children: [_jsx("span", { className: 'text-sm font-bold text-ink', children: title }), badge === undefined ? null : (_jsx("span", { className: 'rounded-full border border-warning/40 px-2.5 py-1 text-xs font-semibold text-warning', children: badge }))] }), _jsx("ul", { className: 'flex flex-col', children: children }), note === undefined ? null : (_jsx("p", { className: 'mt-4 border-t border-line/40 pt-4 text-xs leading-5 text-ink/45', children: note }))] }));
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PsStepMarkerClassNames } from '../constants/ps-step-marker-class-names';
3
+ import { PsStepState } from '../constants/ps-step-state';
4
+ export const PsStepList = ({ steps }) => (_jsx("ol", { className: 'flex flex-col rounded-ps-panel border border-line bg-surface', children: steps.map((step, index) => (_jsxs("li", { "aria-current": step.state === PsStepState.current ? 'step' : undefined, className: 'relative flex gap-4 border-b border-line/70 px-5 py-4 last:border-0', children: [_jsx("span", { className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-full border text-sm font-semibold ${PsStepMarkerClassNames[step.state]}`, children: step.state === PsStepState.done ? _jsx("span", { "aria-hidden": 'true', children: "\u2713" }) : index + 1 }), _jsxs("div", { className: 'flex min-w-0 flex-1 flex-col gap-3', children: [_jsxs("div", { className: 'flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between', children: [_jsxs("div", { className: 'min-w-0', children: [_jsx("h3", { className: step.state === PsStepState.upcoming
5
+ ? 'text-sm font-semibold text-ink/50'
6
+ : 'text-sm font-semibold text-ink', children: step.title }), _jsx("p", { className: 'mt-0.5 text-sm text-ink/60', children: step.description })] }), step.actions ? (_jsx("div", { className: 'flex shrink-0 flex-wrap items-center gap-2', children: step.actions })) : null] }), step.content ? _jsx("div", { className: 'min-w-0', children: step.content }) : null] })] }, step.id))) }));
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const PsSummaryBar = ({ items }) => (_jsx("dl", { className: 'grid grid-cols-2 gap-x-6 gap-y-4 rounded-ps-panel border border-line bg-surface px-5 py-4 sm:flex sm:flex-wrap sm:items-center sm:gap-x-10', children: items.map((item) => (_jsxs("div", { className: 'flex min-w-0 flex-col gap-1', children: [_jsx("dt", { className: 'text-xs font-medium uppercase tracking-wide text-ink/50', children: item.label }), _jsx("dd", { className: item.emphasis
3
+ ? 'truncate text-xl font-semibold tabular-nums text-ink'
4
+ : 'truncate text-sm font-medium text-ink', children: item.value })] }, item.label))) }));
@@ -0,0 +1,3 @@
1
+ export const PsCappedListConfig = {
2
+ defaultVisibleRows: 5,
3
+ };
@@ -0,0 +1,6 @@
1
+ import { PsStepState } from './ps-step-state';
2
+ export const PsStepMarkerClassNames = {
3
+ [PsStepState.done]: 'border-success bg-success text-white',
4
+ [PsStepState.current]: 'border-primary bg-primary/10 text-primary',
5
+ [PsStepState.upcoming]: 'border-line bg-surface text-ink/40',
6
+ };
@@ -0,0 +1,5 @@
1
+ export const PsStepState = {
2
+ current: 'current',
3
+ done: 'done',
4
+ upcoming: 'upcoming',
5
+ };
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from 'react';
2
+ export const usePsDismissable = (isOpen, onDismiss) => {
3
+ const containerRef = useRef(null);
4
+ useEffect(() => {
5
+ if (!isOpen)
6
+ return;
7
+ const handlePointer = (event) => {
8
+ if (event.target instanceof Node &&
9
+ containerRef.current &&
10
+ !containerRef.current.contains(event.target))
11
+ onDismiss();
12
+ };
13
+ const handleKey = (event) => {
14
+ if (event.key === 'Escape')
15
+ onDismiss();
16
+ };
17
+ document.addEventListener('pointerdown', handlePointer);
18
+ document.addEventListener('keydown', handleKey);
19
+ return () => {
20
+ document.removeEventListener('pointerdown', handlePointer);
21
+ document.removeEventListener('keydown', handleKey);
22
+ };
23
+ }, [isOpen, onDismiss]);
24
+ return containerRef;
25
+ };
package/dist/esm/index.js CHANGED
@@ -107,3 +107,17 @@ export * from './models/ps-link-tile-data';
107
107
  export * from './models/ps-metric-item-data';
108
108
  export * from './models/ps-theme-mode-state';
109
109
  export * from './models/ps-tool-card-data';
110
+ export * from './components/ps-capped-list';
111
+ export * from './components/ps-detail-list';
112
+ export * from './components/ps-form-footer';
113
+ export * from './components/ps-start-highlight';
114
+ export * from './components/ps-start-panel';
115
+ export * from './components/ps-start-preview';
116
+ export * from './components/ps-step-list';
117
+ export * from './components/ps-summary-bar';
118
+ export * from './constants/ps-step-state';
119
+ export * from './hooks/use-ps-dismissable';
120
+ export * from './models/ps-detail-list-item';
121
+ export * from './models/ps-start-panel-step';
122
+ export * from './models/ps-step-list-item';
123
+ export * from './models/ps-summary-item';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from 'react';
2
+ export declare const usePsDismissable: <Element extends HTMLElement>(isOpen: boolean, onDismiss: () => void) => RefObject<Element | null>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsDismissable = void 0;
4
+ const react_1 = require("react");
5
+ const usePsDismissable = (isOpen, onDismiss) => {
6
+ const containerRef = (0, react_1.useRef)(null);
7
+ (0, react_1.useEffect)(() => {
8
+ if (!isOpen)
9
+ return;
10
+ const handlePointer = (event) => {
11
+ if (event.target instanceof Node &&
12
+ containerRef.current &&
13
+ !containerRef.current.contains(event.target))
14
+ onDismiss();
15
+ };
16
+ const handleKey = (event) => {
17
+ if (event.key === 'Escape')
18
+ onDismiss();
19
+ };
20
+ document.addEventListener('pointerdown', handlePointer);
21
+ document.addEventListener('keydown', handleKey);
22
+ return () => {
23
+ document.removeEventListener('pointerdown', handlePointer);
24
+ document.removeEventListener('keydown', handleKey);
25
+ };
26
+ }, [isOpen, onDismiss]);
27
+ return containerRef;
28
+ };
29
+ exports.usePsDismissable = usePsDismissable;
package/dist/index.d.ts CHANGED
@@ -107,3 +107,17 @@ export * from './models/ps-link-tile-data';
107
107
  export * from './models/ps-metric-item-data';
108
108
  export * from './models/ps-theme-mode-state';
109
109
  export * from './models/ps-tool-card-data';
110
+ export * from './components/ps-capped-list';
111
+ export * from './components/ps-detail-list';
112
+ export * from './components/ps-form-footer';
113
+ export * from './components/ps-start-highlight';
114
+ export * from './components/ps-start-panel';
115
+ export * from './components/ps-start-preview';
116
+ export * from './components/ps-step-list';
117
+ export * from './components/ps-summary-bar';
118
+ export * from './constants/ps-step-state';
119
+ export * from './hooks/use-ps-dismissable';
120
+ export * from './models/ps-detail-list-item';
121
+ export * from './models/ps-start-panel-step';
122
+ export * from './models/ps-step-list-item';
123
+ export * from './models/ps-summary-item';
package/dist/index.js CHANGED
@@ -123,3 +123,17 @@ __exportStar(require("./models/ps-link-tile-data"), exports);
123
123
  __exportStar(require("./models/ps-metric-item-data"), exports);
124
124
  __exportStar(require("./models/ps-theme-mode-state"), exports);
125
125
  __exportStar(require("./models/ps-tool-card-data"), exports);
126
+ __exportStar(require("./components/ps-capped-list"), exports);
127
+ __exportStar(require("./components/ps-detail-list"), exports);
128
+ __exportStar(require("./components/ps-form-footer"), exports);
129
+ __exportStar(require("./components/ps-start-highlight"), exports);
130
+ __exportStar(require("./components/ps-start-panel"), exports);
131
+ __exportStar(require("./components/ps-start-preview"), exports);
132
+ __exportStar(require("./components/ps-step-list"), exports);
133
+ __exportStar(require("./components/ps-summary-bar"), exports);
134
+ __exportStar(require("./constants/ps-step-state"), exports);
135
+ __exportStar(require("./hooks/use-ps-dismissable"), exports);
136
+ __exportStar(require("./models/ps-detail-list-item"), exports);
137
+ __exportStar(require("./models/ps-start-panel-step"), exports);
138
+ __exportStar(require("./models/ps-step-list-item"), exports);
139
+ __exportStar(require("./models/ps-summary-item"), exports);
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsDetailListItem {
3
+ label: string;
4
+ value: ReactNode;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface PsStartPanelStep {
2
+ description: string;
3
+ id: string;
4
+ title: string;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PsStepState } from '../constants/ps-step-state';
3
+ export interface PsStepListItem {
4
+ actions?: ReactNode;
5
+ content?: ReactNode;
6
+ description: string;
7
+ id: string;
8
+ state: PsStepState;
9
+ title: string;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface PsSummaryItem {
3
+ emphasis?: boolean;
4
+ label: string;
5
+ value: ReactNode;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-core-components",
3
- "version": "2.19.0",
3
+ "version": "2.20.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -28,7 +28,8 @@
28
28
  "format:prettier": "npx prettier --write \"src/**/*.{ts,tsx,json,md}\"",
29
29
  "lint:eslint": "npx eslint \"src/**/*.{ts,tsx}\"",
30
30
  "lint:eslint:fix": "npx eslint --fix \"src/**/*.{ts,tsx}\"",
31
- "release": "semantic-release"
31
+ "release": "semantic-release",
32
+ "test": "vitest run"
32
33
  },
33
34
  "dependencies": {
34
35
  "@dnd-kit/core": "^6.3.1",
@@ -69,13 +70,15 @@
69
70
  "eslint-plugin-import": "^2.32.0",
70
71
  "eslint-plugin-prettier": "^5.5.4",
71
72
  "husky": "^9.1.7",
73
+ "jsdom": "^30.1.1",
72
74
  "lint-staged": "^16.2.7",
73
75
  "prettier": "^3.6.2",
74
76
  "react": "^19.3.0",
75
77
  "react-dom": "^19.3.0",
76
78
  "semantic-release": "^24.2.0",
77
79
  "ts-node": "^10.9.2",
78
- "typescript": "^5.9.3"
80
+ "typescript": "^5.9.3",
81
+ "vitest": "^3.2.7"
79
82
  },
80
83
  "publishConfig": {
81
84
  "access": "public",