diff --git a/src/components/molecules/AddOnOption/AddOnOption.stories.tsx b/src/components/molecules/AddOnOption/AddOnOption.stories.tsx
index 7e3f998..4183714 100644
--- a/src/components/molecules/AddOnOption/AddOnOption.stories.tsx
+++ b/src/components/molecules/AddOnOption/AddOnOption.stories.tsx
@@ -182,6 +182,36 @@ export const Disabled: Story = {
},
};
+// ─── With Line Limit ────────────────────────────────────────────────────────
+
+/** Clamped descriptions with "View more" toggle */
+export const WithLineLimit: Story = {
+ render: function Render() {
+ const [checks, setChecks] = React.useState({ a: false, b: true });
+
+ return (
+
+ setChecks({ ...checks, a: v })}
+ />
+ setChecks({ ...checks, b: v })}
+ />
+
+ );
+ },
+};
+
// ─── Edge Cases ─────────────────────────────────────────────────────────────
/** Edge cases — long text, high prices, missing fields */
diff --git a/src/components/molecules/AddOnOption/AddOnOption.tsx b/src/components/molecules/AddOnOption/AddOnOption.tsx
index 995274c..ffbbc07 100644
--- a/src/components/molecules/AddOnOption/AddOnOption.tsx
+++ b/src/components/molecules/AddOnOption/AddOnOption.tsx
@@ -4,6 +4,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
import { Card } from '../../atoms/Card';
import { Typography } from '../../atoms/Typography';
import { Switch } from '../../atoms/Switch';
+import { Link } from '../../atoms/Link';
// ─── Types ───────────────────────────────────────────────────────────────────
@@ -13,7 +14,7 @@ export interface AddOnOptionProps {
name: string;
/** Description text explaining the add-on */
description?: string;
- /** Price in dollars — shown after the heading */
+ /** Price in dollars — shown below the heading */
price?: number;
/** Whether this add-on is currently enabled */
checked?: boolean;
@@ -21,6 +22,8 @@ export interface AddOnOptionProps {
onChange?: (checked: boolean) => void;
/** Whether this add-on is disabled/unavailable */
disabled?: boolean;
+ /** Max visible lines for description before "View more" toggle. Omit for no limit. */
+ maxDescriptionLines?: number;
/** MUI sx prop for style overrides */
sx?: SxProps;
}
@@ -51,8 +54,19 @@ export interface AddOnOptionProps {
* ```
*/
export const AddOnOption = React.forwardRef(
- ({ name, description, price, checked = false, onChange, disabled = false, sx }, ref) => {
+ ({ name, description, price, checked = false, onChange, disabled = false, maxDescriptionLines, sx }, ref) => {
const switchId = React.useId();
+ const [expanded, setExpanded] = React.useState(false);
+ const [isClamped, setIsClamped] = React.useState(false);
+ const descRef = React.useRef(null);
+
+ // Detect whether the description is actually truncated
+ React.useEffect(() => {
+ const el = descRef.current;
+ if (el && maxDescriptionLines) {
+ setIsClamped(el.scrollHeight > el.clientHeight + 1);
+ }
+ }, [description, maxDescriptionLines]);
const handleToggle = () => {
if (!disabled && onChange) {
@@ -85,33 +99,23 @@ export const AddOnOption = React.forwardRef(
...(Array.isArray(sx) ? sx : [sx]),
]}
>
- {/* Heading row: name + optional price + switch */}
+ {/* Top row: name + switch pinned top-right */}
-
-
- {name}
-
- {price != null && (
-
- ${price.toLocaleString('en-AU')}
-
- )}
-
+
+ {name}
+
(
/>
- {/* Description */}
- {description && (
+ {/* Price — own row below heading */}
+ {price != null && (
- {description}
+ ${price.toLocaleString('en-AU')}
)}
+
+ {/* Description with optional line clamping */}
+ {description && (
+ <>
+
+ {description}
+
+ {maxDescriptionLines && isClamped && (
+ {
+ e.stopPropagation();
+ setExpanded((prev) => !prev);
+ }}
+ sx={{ mt: 0.5, fontSize: 'inherit' }}
+ >
+ {expanded ? 'View less' : 'View more'}
+
+ )}
+ >
+ )}
);
},
diff --git a/src/components/molecules/ServiceOption/ServiceOption.stories.tsx b/src/components/molecules/ServiceOption/ServiceOption.stories.tsx
index 23a3a52..8d9f6c5 100644
--- a/src/components/molecules/ServiceOption/ServiceOption.stories.tsx
+++ b/src/components/molecules/ServiceOption/ServiceOption.stories.tsx
@@ -202,6 +202,41 @@ export const Disabled: Story = {
),
};
+// ─── With Line Limit ────────────────────────────────────────────────────────
+
+/** Clamped descriptions with "View more" toggle */
+export const WithLineLimit: Story = {
+ name: 'With Line Limit',
+ render: () => {
+ const [selected, setSelected] = useState('');
+
+ return (
+
+ setSelected('complete')}
+ />
+ setSelected('cremation')}
+ />
+
+ );
+ },
+};
+
// ─── Edge Cases ─────────────────────────────────────────────────────────────
/** Edge cases: long text, no description, high price */
diff --git a/src/components/molecules/ServiceOption/ServiceOption.tsx b/src/components/molecules/ServiceOption/ServiceOption.tsx
index bf93bfe..bb206d4 100644
--- a/src/components/molecules/ServiceOption/ServiceOption.tsx
+++ b/src/components/molecules/ServiceOption/ServiceOption.tsx
@@ -3,6 +3,7 @@ import Box from '@mui/material/Box';
import type { SxProps, Theme } from '@mui/material/styles';
import { Card } from '../../atoms/Card';
import { Typography } from '../../atoms/Typography';
+import { Link } from '../../atoms/Link';
// ─── Types ───────────────────────────────────────────────────────────────────
@@ -20,6 +21,8 @@ export interface ServiceOptionProps {
disabled?: boolean;
/** Click handler — toggles selection */
onClick?: () => void;
+ /** Max visible lines for description before "View more" toggle. Omit for no limit. */
+ maxDescriptionLines?: number;
/** MUI sx prop for style overrides */
sx?: SxProps;
}
@@ -52,7 +55,19 @@ export interface ServiceOptionProps {
* ```
*/
export const ServiceOption = React.forwardRef(
- ({ name, description, price, selected = false, disabled = false, onClick, sx }, ref) => {
+ ({ name, description, price, selected = false, disabled = false, onClick, maxDescriptionLines, sx }, ref) => {
+ const [expanded, setExpanded] = React.useState(false);
+ const [isClamped, setIsClamped] = React.useState(false);
+ const descRef = React.useRef(null);
+
+ // Detect whether the description is actually truncated
+ React.useEffect(() => {
+ const el = descRef.current;
+ if (el && maxDescriptionLines) {
+ setIsClamped(el.scrollHeight > el.clientHeight + 1);
+ }
+ }, [description, maxDescriptionLines]);
+
return (
- {/* Description */}
+ {/* Description with optional line clamping */}
{description && (
-
- {description}
-
+ <>
+
+ {description}
+
+ {maxDescriptionLines && isClamped && (
+ {
+ e.stopPropagation();
+ setExpanded((prev) => !prev);
+ }}
+ sx={{ mt: 0.5, fontSize: 'inherit' }}
+ >
+ {expanded ? 'View less' : 'View more'}
+
+ )}
+ >
)}
);