Repository navigation
Expand file tree
/
Copy pathCard.ts
More file actions
131 lines (119 loc) · 4.01 KB
/
Copy pathCard.ts
File metadata and controls
131 lines (119 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
* This file belongs to Hoist, an application development toolkit
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
*
* Copyright © 2026 Extremely Heavy Industries Inc.
*/
import {box, fieldset} from '@xh/hoist/cmp/layout';
import {
BoxProps,
DomAttrsProps,
hoistCmp,
HoistProps,
Intent,
LayoutProps,
TestSupportProps,
uses,
XH
} from '@xh/hoist/core';
import {cardHeaderImpl as desktopCardHeaderImpl} from '@xh/hoist/dynamics/desktop';
import {cardHeaderImpl as mobileCardHeaderImpl} from '@xh/hoist/dynamics/mobile';
import {mergeDeep, TEST_ID, warnIf} from '@xh/hoist/utils/js';
import {splitLayoutProps} from '@xh/hoist/utils/react';
import classNames from 'classnames';
import {type ReactElement, type ReactNode, useRef} from 'react';
import {CardModel} from './CardModel';
import './Card.scss';
export interface CardProps<M extends CardModel = CardModel>
extends HoistProps<M>, TestSupportProps, DomAttrsProps, LayoutProps {
/** An icon placed left of the title. */
icon?: ReactElement;
/** Intent to apply to the inline header and border. */
intent?: Intent;
/** The title to display. */
title?: ReactNode;
/** Tooltip to show when hovering over the inline header. */
tooltip?: ReactElement | string;
/** Additional props to pass to the inner box hosting the content. */
contentBoxProps?: BoxProps;
}
/**
* A bordered container for grouping related content, rendered as an HTML fieldset/legend.
* Supports an optional inline header (`title`, `icon`, `intent`) and user-driven
* collapse/expand via its backing {@link CardModel}.
*
* Children are arranged vertically in a flexbox container by default - pass
* `contentBoxProps` to customize the content layout.
*
* @see CardModel
* @see FormFieldSet
*/
export const [Card, card] = hoistCmp.withFactory<CardProps>({
displayName: 'Card',
model: uses(CardModel, {
fromContext: false,
publishMode: 'limited',
createDefault: true
}),
className: 'xh-card',
render({
icon,
title,
tooltip,
intent,
children,
className,
contentBoxProps = {},
model,
...rest
}) {
const {isMobileApp} = XH;
warnIf(tooltip && isMobileApp, 'Tooltips are not supported on mobile - will be ignored.');
const wasDisplayed = useRef(false),
{collapsed, renderMode} = model;
let [layoutProps, {testId, domAttrs, ...restProps}] = splitLayoutProps(rest);
restProps = mergeDeep({style: layoutProps}, {[TEST_ID]: testId, ...domAttrs}, restProps);
const classes: string[] = [];
if (collapsed) {
classes.push('xh-card--collapsed');
} else {
classes.push('xh-card--expanded');
wasDisplayed.current = true;
}
if (intent) {
classes.push(`xh-card--intent-${intent}`);
} else {
classes.push(`xh-card--intent-none`);
}
const cardHeader = XH.isMobileApp ? mobileCardHeaderImpl : desktopCardHeaderImpl;
const items: ReactNode = (() => {
switch (renderMode) {
case 'always':
return children;
case 'lazy':
return collapsed && !wasDisplayed.current ? [] : children;
case 'unmountOnHide':
return collapsed ? [] : children;
}
})();
return fieldset({
className: classNames(className, classes),
items: [
cardHeader({
icon,
intent,
model,
title,
tooltip
}),
box({
...contentBoxProps,
className: classNames('xh-card__content', contentBoxProps?.className),
items,
display: collapsed ? 'none' : 'flex'
})
],
...restProps
});
}
});