@octopusdeploy/design-system-components
Version:
The design systems component library.
120 lines (119 loc) • 6.48 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FormSection = FormSection;
const jsx_runtime_1 = require("react/jsx-runtime");
const css_1 = require("@emotion/css");
const design_system_icons_1 = require("@octopusdeploy/design-system-icons");
const design_system_tokens_1 = require("@octopusdeploy/design-system-tokens");
const react_1 = require("react");
const markerLabels = {
optional: "(optional)",
required: "(required)",
prefilled: "(pre-filled)",
};
/**
* Groups related form fields under a distinctive legend bar.
* @remarks Rollout currently paused — do not use in new code until further notice.
*/
function FormSection(props) {
const contentId = (0, react_1.useId)();
const isInteractive = props.isInteractive === true;
const isOpen = !isInteractive || props.isOpen;
return ((0, jsx_runtime_1.jsxs)("fieldset", { className: styles.root, children: [props.isInteractive ? ((0, jsx_runtime_1.jsx)("legend", { className: styles.interactiveWrapper, children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: styles.interactiveButton, "aria-expanded": props.isOpen, "aria-controls": contentId, onClick: () => props.onToggle(!props.isOpen), children: [(0, jsx_runtime_1.jsxs)("span", { className: styles.interactiveLeftGroup, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles.titleAndMarker, children: [(0, jsx_runtime_1.jsx)("span", { children: props.title }), props.marker && (0, jsx_runtime_1.jsx)("span", { children: markerLabels[props.marker] })] }), props.badge] }), (0, jsx_runtime_1.jsx)("span", { className: (0, css_1.cx)(styles.chevron, { [styles.chevronOpen]: props.isOpen }), children: (0, jsx_runtime_1.jsx)(design_system_icons_1.ChevronRightIcon, { size: 20, color: design_system_tokens_1.themeTokens.color.button.icon.quiet }) })] }) })) : ((0, jsx_runtime_1.jsxs)("legend", { className: styles.static, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles.titleAndMarker, children: [(0, jsx_runtime_1.jsx)("span", { children: props.title }), props.marker && (0, jsx_runtime_1.jsx)("span", { children: markerLabels[props.marker] })] }), props.badge] })), (0, jsx_runtime_1.jsx)("div", { id: contentId, className: (0, css_1.cx)(styles.content, { [styles.contentClosed]: !isOpen }), inert: isOpen ? undefined : "", children: props.children })] }));
}
const styles = {
root: (0, css_1.css)({
display: "flex",
flexDirection: "column",
padding: 0,
border: 0,
margin: 0,
minWidth: 0,
interpolateSize: "allow-keywords",
// Divider between stacked sections. :last-of-type (rather than :last-child) keeps the
// rule scoped to sibling fieldsets - the only element FormSection renders - so a
// trailing non-fieldset sibling doesn't give the final section a stray border. If the
// divider ever needs to react to other sibling elements, don't broaden this selector;
// push the logic up into an explicit parent component so FormSection keeps to its own
// boundary.
"&:not(:last-of-type)": {
borderBottom: `${design_system_tokens_1.borderWidth[1]} solid ${design_system_tokens_1.themeTokens.color.border.primary}`,
},
}),
static: (0, css_1.css)({
display: "flex",
alignItems: "center",
gap: design_system_tokens_1.space[4],
padding: `${design_system_tokens_1.space[12]} ${design_system_tokens_1.space[16]}`,
backgroundColor: design_system_tokens_1.themeTokens.color.formLegend.background.rest,
font: design_system_tokens_1.text.body.bold.large,
color: design_system_tokens_1.themeTokens.color.text.primary,
width: "100%",
boxSizing: "border-box",
}),
interactiveWrapper: (0, css_1.css)({
padding: 0,
width: "100%",
}),
interactiveButton: (0, css_1.css)({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: design_system_tokens_1.space[8],
width: "100%",
padding: `${design_system_tokens_1.space[8]} ${design_system_tokens_1.space[8]} ${design_system_tokens_1.space[8]} ${design_system_tokens_1.space[16]}`,
textAlign: "left",
font: design_system_tokens_1.text.body.bold.large,
color: design_system_tokens_1.themeTokens.color.text.primary,
backgroundColor: design_system_tokens_1.themeTokens.color.formLegend.background.rest,
transition: "background-color 150ms ease-in-out",
border: "none",
cursor: "pointer",
"&:hover": {
backgroundColor: design_system_tokens_1.themeTokens.color.formLegend.background.hover,
},
"&:focus-visible": {
outline: `2px solid ${design_system_tokens_1.themeTokens.color.border.selected}`,
outlineOffset: "-2px",
},
}),
interactiveLeftGroup: (0, css_1.css)({
display: "flex",
alignItems: "center",
gap: design_system_tokens_1.space[4],
}),
chevron: (0, css_1.css)({
display: "flex",
padding: `${design_system_tokens_1.space[6]} ${design_system_tokens_1.space[6]} ${design_system_tokens_1.space[6]} ${design_system_tokens_1.space[0]}`,
svg: {
transition: "transform 150ms ease-in-out",
},
}),
chevronOpen: (0, css_1.css)({
svg: {
transform: "rotate(90deg)",
},
}),
titleAndMarker: (0, css_1.css)({
display: "flex",
alignItems: "center",
gap: design_system_tokens_1.space[2],
}),
content: (0, css_1.css)({
boxSizing: "border-box",
width: "100%",
padding: `${design_system_tokens_1.space[16]} ${design_system_tokens_1.space[16]} ${design_system_tokens_1.space[24]} ${design_system_tokens_1.space[16]} `,
blockSize: "auto",
opacity: 1,
overflow: "hidden",
transition: "block-size 200ms ease-in-out, padding-block 200ms ease-in-out, opacity 100ms ease-in-out 100ms",
}),
contentClosed: (0, css_1.css)({
blockSize: 0,
// box-sizing: border-box can't shrink the box below its block padding, so zero it
// too - otherwise a closed section keeps 40px of empty space below the legend.
paddingBlock: 0,
opacity: 0,
transition: "block-size 200ms ease-in-out, padding-block 200ms ease-in-out, opacity 100ms ease-in-out",
}),
};