@arc-ui/community-components 3.15.0 → 3.17.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.
- package/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +27 -0
- package/lib/ArticleCarousel/ArticleCarousel.cjs +1 -1
- package/lib/ArticleCarousel/ArticleCarousel.mjs +1 -1
- package/lib/ComparisonTable/ComparisonTable.cjs +317 -0
- package/lib/ComparisonTable/ComparisonTable.mjs +310 -0
- package/lib/ComparisonTable/styles.css +1 -0
- package/lib/CopyLead/CopyLead.cjs +1 -1
- package/lib/CopyLead/CopyLead.mjs +1 -1
- package/lib/FeaturePost/FeaturePost.cjs +13 -12
- package/lib/FeaturePost/FeaturePost.mjs +13 -12
- package/lib/FeaturePost/styles.css +1 -1
- package/lib/Highlights/Highlights.cjs +7 -7
- package/lib/Highlights/Highlights.mjs +7 -7
- package/lib/ProductCard/ProductCard.cjs +13 -35
- package/lib/ProductCard/ProductCard.mjs +12 -34
- package/lib/ProductNavigation/ProductNavigation.cjs +1 -1
- package/lib/ProductNavigation/ProductNavigation.mjs +1 -1
- package/lib/PromoListing/PromoListing.cjs +1 -1
- package/lib/PromoListing/PromoListing.mjs +1 -1
- package/lib/Summary/Summary.cjs +1 -1
- package/lib/Summary/Summary.mjs +1 -1
- package/lib/_shared/cjs/ProductCardPriceIncrease-BP39clav.cjs +38 -0
- package/lib/_shared/cjs/{index.es-C-y6aRIY.cjs → index.es-oEhoU2Bd.cjs} +1 -1
- package/lib/_shared/esm/ProductCardPriceIncrease-CJyG2wv-.mjs +35 -0
- package/lib/_shared/esm/{index.es-YxZ1vAot.mjs → index.es-DujqqZXe.mjs} +1 -1
- package/lib/index.cjs +508 -200
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +213 -22
- package/lib/index.d.mts +213 -22
- package/lib/index.mjs +503 -201
- package/lib/index.mjs.map +1 -1
- package/lib/styles.css +1 -1
- package/package.json +16 -16
- package/src/components/ComparisonTable/ComparisonTable.module.css +503 -0
- package/src/components/ComparisonTable/ComparisonTable.tsx +104 -0
- package/src/components/ComparisonTable/components/Cell/Cell.tsx +44 -0
- package/src/components/ComparisonTable/components/ControlCell/ControlCell.tsx +21 -0
- package/src/components/ComparisonTable/components/DesktopRow/DesktopRow.tsx +59 -0
- package/src/components/ComparisonTable/components/DesktopTable/DesktopTable.tsx +150 -0
- package/src/components/ComparisonTable/components/FeatureCell/FeatureCell.module.css +56 -0
- package/src/components/ComparisonTable/components/FeatureCell/FeatureCell.tsx +96 -0
- package/src/components/ComparisonTable/components/HeaderCell/HeaderCell.tsx +59 -0
- package/src/components/ComparisonTable/components/MobileRow/MobileRow.tsx +77 -0
- package/src/components/ComparisonTable/components/MobileTable/MobileTable.tsx +103 -0
- package/src/components/ComparisonTable/components/Section/Section.tsx +75 -0
- package/src/components/ComparisonTable/components/SectionToggle/SectionToggle.tsx +63 -0
- package/src/components/ComparisonTable/components/SubHeaderCell/SubHeaderCell.tsx +62 -0
- package/src/components/ComparisonTable/index.ts +22 -0
- package/src/components/ComparisonTable/types/comparison-column.ts +22 -0
- package/src/components/ComparisonTable/types/comparison-row.ts +23 -0
- package/src/components/ComparisonTable/types/comparison-section.ts +39 -0
- package/src/components/ComparisonTable/types/comparison-sub-row.ts +8 -0
- package/src/components/ComparisonTable/utils/group-rows.ts +69 -0
- package/src/components/ComparisonTable/utils/use-section-state.ts +45 -0
- package/src/components/FeaturePost/FeaturePost.module.css +24 -2
- package/src/components/FeaturePost/FeaturePost.tsx +26 -25
- package/src/components/FeaturePost/types/feature-post-cta-footer.ts +4 -3
- package/src/components/Highlights/Highlights.tsx +5 -32
- package/src/components/ProductCard/ProductCard.tsx +14 -14
- package/src/components/ProductCard/ProductCardPrice/ProductCardPrice.tsx +11 -3
- package/src/components/ProductCard/ProductCardPriceIncrease/ProductCardPriceIncrease.module.css +1 -2
- package/src/components/ProductCard/ProductCardPriceIncrease/ProductCardPriceIncrease.tsx +14 -3
- package/src/components/ProductCardGrid/ProductCardGrid.tsx +8 -8
- package/src/components/ProductCardGrid/components/CardGrid/CardGrid.tsx +6 -6
- package/src/components/index.ts +1 -0
- package/versions.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
import { Heading, type HeadingLevel } from "@arc-ui/components/Heading";
|
|
5
|
+
import { Icon } from "@arc-ui/components/Icon";
|
|
6
|
+
import { BtIconChevronDownMid } from "@arc-ui/icons/BtIconChevronDownMid";
|
|
7
|
+
|
|
8
|
+
import styles from "../../ComparisonTable.module.css";
|
|
9
|
+
|
|
10
|
+
export const SectionToggle: React.FC<SectionToggleProps> = ({
|
|
11
|
+
heading,
|
|
12
|
+
headingLevel = "3",
|
|
13
|
+
isOpen,
|
|
14
|
+
onToggle,
|
|
15
|
+
controlsId,
|
|
16
|
+
}) => (
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
className={classNames(styles.sectionToggle, {
|
|
20
|
+
[styles.sectionToggleOpen]: isOpen,
|
|
21
|
+
})}
|
|
22
|
+
aria-expanded={isOpen}
|
|
23
|
+
aria-controls={controlsId}
|
|
24
|
+
onClick={onToggle}
|
|
25
|
+
>
|
|
26
|
+
<span className={styles.sectionToggleContent}>
|
|
27
|
+
<Heading level={headingLevel} size="s">
|
|
28
|
+
{heading}
|
|
29
|
+
</Heading>
|
|
30
|
+
<span
|
|
31
|
+
className={classNames(styles.sectionChevron, {
|
|
32
|
+
[styles.sectionChevronOpen]: isOpen,
|
|
33
|
+
})}
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
>
|
|
36
|
+
<Icon icon={BtIconChevronDownMid} size={24} />
|
|
37
|
+
</span>
|
|
38
|
+
</span>
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export interface SectionToggleProps {
|
|
43
|
+
/**
|
|
44
|
+
* Heading text/content shown in the toggle.
|
|
45
|
+
*/
|
|
46
|
+
heading: React.ReactNode;
|
|
47
|
+
/**
|
|
48
|
+
* Heading level for the section heading. Defaults to `"3"`.
|
|
49
|
+
*/
|
|
50
|
+
headingLevel?: HeadingLevel;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the section is currently open; drives `aria-expanded` and the chevron rotation.
|
|
53
|
+
*/
|
|
54
|
+
isOpen: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Called when the toggle is activated.
|
|
57
|
+
*/
|
|
58
|
+
onToggle: () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Id of the collapsible region this toggle controls, for `aria-controls`.
|
|
61
|
+
*/
|
|
62
|
+
controlsId: string;
|
|
63
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
import { filterAttrs } from "@arc-ui/community-utils/filter-attrs";
|
|
5
|
+
import { ButtonV2, type ButtonV2Props } from "@arc-ui/components/ButtonV2";
|
|
6
|
+
import { VerticalSpace } from "@arc-ui/components/VerticalSpace";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ProductCardPrice,
|
|
10
|
+
type ProductCardPriceProps,
|
|
11
|
+
} from "../../../ProductCard/ProductCardPrice";
|
|
12
|
+
import {
|
|
13
|
+
ProductCardPriceIncrease,
|
|
14
|
+
type ProductCardPriceIncreaseProps,
|
|
15
|
+
} from "../../../ProductCard/ProductCardPriceIncrease";
|
|
16
|
+
import styles from "../../ComparisonTable.module.css";
|
|
17
|
+
|
|
18
|
+
export const SubHeaderCell: React.FC<SubHeaderCellProps> = ({
|
|
19
|
+
price,
|
|
20
|
+
priceIncrease,
|
|
21
|
+
button,
|
|
22
|
+
columnIndex = 0,
|
|
23
|
+
...props
|
|
24
|
+
}) => (
|
|
25
|
+
<div
|
|
26
|
+
role="cell"
|
|
27
|
+
className={classNames(styles.subHeaderCell, {
|
|
28
|
+
[styles.highlighted]: columnIndex % 2 === 0,
|
|
29
|
+
})}
|
|
30
|
+
{...filterAttrs(props)}
|
|
31
|
+
>
|
|
32
|
+
<ProductCardPrice {...price} />
|
|
33
|
+
{priceIncrease && (
|
|
34
|
+
<ProductCardPriceIncrease
|
|
35
|
+
heading="Increasing to:"
|
|
36
|
+
hideBorder
|
|
37
|
+
{...priceIncrease}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
<VerticalSpace />
|
|
41
|
+
{button && <ButtonV2 isFullWidth {...button} />}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export interface SubHeaderCellProps {
|
|
46
|
+
/**
|
|
47
|
+
* Price shown at the top of the sub-header cell (prefix, value, suffix).
|
|
48
|
+
*/
|
|
49
|
+
price: ProductCardPriceProps;
|
|
50
|
+
/**
|
|
51
|
+
* Optional upcoming price-increase details rendered beneath the price.
|
|
52
|
+
*/
|
|
53
|
+
priceIncrease?: ProductCardPriceIncreaseProps;
|
|
54
|
+
/**
|
|
55
|
+
* Optional call-to-action button rendered at the bottom of the cell. Rendered full-width.
|
|
56
|
+
*/
|
|
57
|
+
button?: ButtonV2Props;
|
|
58
|
+
/**
|
|
59
|
+
* Zero-based index of the column this cell belongs to. Used to highlight every other column, starting with the first.
|
|
60
|
+
*/
|
|
61
|
+
columnIndex?: number;
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { ComparisonTable, type ComparisonTableProps } from "./ComparisonTable";
|
|
2
|
+
export {
|
|
3
|
+
HeaderCell,
|
|
4
|
+
type HeaderCellProps,
|
|
5
|
+
} from "./components/HeaderCell/HeaderCell";
|
|
6
|
+
export {
|
|
7
|
+
SubHeaderCell,
|
|
8
|
+
type SubHeaderCellProps,
|
|
9
|
+
} from "./components/SubHeaderCell/SubHeaderCell";
|
|
10
|
+
export { Cell, type CellProps } from "./components/Cell/Cell";
|
|
11
|
+
export {
|
|
12
|
+
ControlCell,
|
|
13
|
+
type ControlCellProps,
|
|
14
|
+
} from "./components/ControlCell/ControlCell";
|
|
15
|
+
export {
|
|
16
|
+
FeatureCell,
|
|
17
|
+
type FeatureCellProps,
|
|
18
|
+
} from "./components/FeatureCell/FeatureCell";
|
|
19
|
+
export { type ComparisonColumn } from "./types/comparison-column";
|
|
20
|
+
export { type ComparisonRow } from "./types/comparison-row";
|
|
21
|
+
export { type ComparisonSubRow } from "./types/comparison-sub-row";
|
|
22
|
+
export { type ComparisonSection } from "./types/comparison-section";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SubHeaderCellProps } from "../components/SubHeaderCell/SubHeaderCell";
|
|
2
|
+
|
|
3
|
+
export interface ComparisonColumn {
|
|
4
|
+
/**
|
|
5
|
+
* Stable identifier for the column; used as a React key.
|
|
6
|
+
*/
|
|
7
|
+
id: string;
|
|
8
|
+
/**
|
|
9
|
+
* Visible label rendered in the column header cell (e.g. a plan name).
|
|
10
|
+
*/
|
|
11
|
+
label: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional promotional text displayed in a branded banner above the column
|
|
14
|
+
* header label. When set, the banner is rendered inside the header cell with
|
|
15
|
+
* a dark brand background.
|
|
16
|
+
*/
|
|
17
|
+
promoted?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional sub-header content (price, upcoming price increase, call-to-action) rendered in a row beneath the header. When any column defines this, the sub-header row is shown.
|
|
20
|
+
*/
|
|
21
|
+
subheader?: SubHeaderCellProps;
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { type ComparisonSubRow } from "./comparison-sub-row";
|
|
4
|
+
import { type FeatureCellProps } from "../components/FeatureCell/FeatureCell";
|
|
5
|
+
|
|
6
|
+
export interface ComparisonRow {
|
|
7
|
+
/**
|
|
8
|
+
* Feature label rendered as the row's full-width header, spanning all columns above the content row(s).
|
|
9
|
+
*/
|
|
10
|
+
feature: FeatureCellProps;
|
|
11
|
+
/**
|
|
12
|
+
* One cell per column for the row's primary content row, in the same order as `columns`. Length MUST equal columns.length.
|
|
13
|
+
*/
|
|
14
|
+
cells: React.ReactNode[];
|
|
15
|
+
/**
|
|
16
|
+
* Optional additional content rows rendered beneath the primary row, for the less common case where a feature spans multiple rows.
|
|
17
|
+
*/
|
|
18
|
+
subRows?: ComparisonSubRow[];
|
|
19
|
+
/**
|
|
20
|
+
* Id of the `ComparisonSection` this row belongs to. Omit to render the row before all sections. An id with no matching section logs a dev warning and renders the row unsectioned.
|
|
21
|
+
*/
|
|
22
|
+
sectionId?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { type HeadingLevel } from "@arc-ui/components/Heading";
|
|
4
|
+
|
|
5
|
+
import { type ComparisonRow } from "./comparison-row";
|
|
6
|
+
|
|
7
|
+
export interface ComparisonSection {
|
|
8
|
+
/**
|
|
9
|
+
* Stable identifier for the section; used as a React key and to link the toggle to its content region for accessibility.
|
|
10
|
+
*/
|
|
11
|
+
id: string;
|
|
12
|
+
/**
|
|
13
|
+
* Heading shown in the section's toggle button.
|
|
14
|
+
*/
|
|
15
|
+
heading: React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Heading level for the section heading, for correct document outline. Defaults to a sensible level.
|
|
18
|
+
*/
|
|
19
|
+
headingLevel?: HeadingLevel;
|
|
20
|
+
/**
|
|
21
|
+
* Initial open state when the section is uncontrolled. Defaults to `false`.
|
|
22
|
+
*/
|
|
23
|
+
defaultOpen?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Controlled open state. When provided, the section is controlled and `onOpenChange` should update it.
|
|
26
|
+
*/
|
|
27
|
+
open?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Called with the next open state when the toggle is activated. Required for controlled usage; optional otherwise.
|
|
30
|
+
*/
|
|
31
|
+
onOpenChange?: (open: boolean) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Internal: a section paired with its rows after grouping. Not part of the public API.
|
|
36
|
+
*/
|
|
37
|
+
export type ResolvedSection = ComparisonSection & {
|
|
38
|
+
rows: ComparisonRow[];
|
|
39
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ComparisonRow } from "../types/comparison-row";
|
|
2
|
+
import {
|
|
3
|
+
type ComparisonSection,
|
|
4
|
+
type ResolvedSection,
|
|
5
|
+
} from "../types/comparison-section";
|
|
6
|
+
|
|
7
|
+
export interface GroupedRows {
|
|
8
|
+
/**
|
|
9
|
+
* Rows without a matching section, rendered before all sections, in input order.
|
|
10
|
+
*/
|
|
11
|
+
unsectioned: ComparisonRow[];
|
|
12
|
+
/**
|
|
13
|
+
* Sections in `sections` array order, each with its rows in input order. Sections with no rows are dropped.
|
|
14
|
+
*/
|
|
15
|
+
sections: ResolvedSection[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Splits a flat list of rows into unsectioned rows and resolved sections.
|
|
20
|
+
* Rows are bucketed by `sectionId` in one pass, then sections are built by
|
|
21
|
+
* iterating `sections` (not the bucket keys) so section order follows the
|
|
22
|
+
* `sections` array. A `sectionId` with no matching section logs a dev-only
|
|
23
|
+
* warning and the row renders unsectioned, so rows are never lost.
|
|
24
|
+
*/
|
|
25
|
+
export const groupRows = (
|
|
26
|
+
rows: ComparisonRow[] = [],
|
|
27
|
+
sections: ComparisonSection[] = [],
|
|
28
|
+
): GroupedRows => {
|
|
29
|
+
const knownIds = new Set(sections.map((section) => section.id));
|
|
30
|
+
const rowsBySection = new Map<string, ComparisonRow[]>();
|
|
31
|
+
const unsectioned: ComparisonRow[] = [];
|
|
32
|
+
|
|
33
|
+
rows.forEach((row) => {
|
|
34
|
+
const { sectionId } = row;
|
|
35
|
+
|
|
36
|
+
if (sectionId === undefined) {
|
|
37
|
+
unsectioned.push(row);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!knownIds.has(sectionId)) {
|
|
42
|
+
if (process.env.NODE_ENV !== "production") {
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.warn(
|
|
45
|
+
`ComparisonTable: row sectionId "${sectionId}" does not match any section; rendering it unsectioned.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
unsectioned.push(row);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bucket = rowsBySection.get(sectionId);
|
|
53
|
+
if (bucket) {
|
|
54
|
+
bucket.push(row);
|
|
55
|
+
} else {
|
|
56
|
+
rowsBySection.set(sectionId, [row]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const resolvedSections: ResolvedSection[] = [];
|
|
61
|
+
sections.forEach((section) => {
|
|
62
|
+
const sectionRows = rowsBySection.get(section.id);
|
|
63
|
+
if (sectionRows?.length) {
|
|
64
|
+
resolvedSections.push({ ...section, rows: sectionRows });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { unsectioned, sections: resolvedSections };
|
|
69
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export interface UseSectionStateOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Initial open state when uncontrolled.
|
|
6
|
+
*/
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Controlled open state. When defined, the hook is controlled.
|
|
10
|
+
*/
|
|
11
|
+
open?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Called with the next open state whenever the section is toggled.
|
|
14
|
+
*/
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Optional-controlled open state for a section. When `open` is provided the
|
|
20
|
+
* section is controlled and the internal state is ignored; otherwise the hook
|
|
21
|
+
* manages its own state seeded by `defaultOpen`. `onOpenChange` is always
|
|
22
|
+
* called with the next value when `toggle` runs.
|
|
23
|
+
*/
|
|
24
|
+
export const useSectionState = ({
|
|
25
|
+
defaultOpen = false,
|
|
26
|
+
open,
|
|
27
|
+
onOpenChange,
|
|
28
|
+
}: UseSectionStateOptions) => {
|
|
29
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
30
|
+
|
|
31
|
+
const isControlled = open !== undefined;
|
|
32
|
+
const isOpen = isControlled ? open : internalOpen;
|
|
33
|
+
|
|
34
|
+
const toggle = useCallback(() => {
|
|
35
|
+
const next = !isOpen;
|
|
36
|
+
|
|
37
|
+
if (!isControlled) {
|
|
38
|
+
setInternalOpen(next);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onOpenChange?.(next);
|
|
42
|
+
}, [isControlled, isOpen, onOpenChange]);
|
|
43
|
+
|
|
44
|
+
return { isOpen, toggle };
|
|
45
|
+
};
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
/* stylelint-disable selector-pseudo-class-no-unknown */
|
|
1
2
|
@import url("@arc-ui/tokens-arc/dist/custom-media.css");
|
|
2
3
|
|
|
4
|
+
.labelContainer {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: baseline;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.labelText {
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
min-width: 0;
|
|
13
|
+
font: var(--sem-type-label-overline);
|
|
14
|
+
letter-spacing: 0.1em;
|
|
15
|
+
text-transform: uppercase;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.labelWordWrap {
|
|
19
|
+
hyphens: auto;
|
|
20
|
+
overflow-wrap: break-word;
|
|
21
|
+
}
|
|
22
|
+
|
|
3
23
|
.labelDate {
|
|
4
24
|
padding-top: var(--sem-space-padding-20);
|
|
5
25
|
text-transform: uppercase;
|
|
@@ -43,8 +63,10 @@
|
|
|
43
63
|
width: 50%;
|
|
44
64
|
}
|
|
45
65
|
|
|
46
|
-
.
|
|
47
|
-
|
|
66
|
+
.labelContainer {
|
|
67
|
+
flex-direction: row;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
column-gap: var(--sem-space-padding-70);
|
|
48
70
|
}
|
|
49
71
|
}
|
|
50
72
|
|
|
@@ -57,31 +57,29 @@ export const FeaturePost: React.FC<FeaturePostProps> = ({
|
|
|
57
57
|
<div className={styles.col}>
|
|
58
58
|
<div className={styles.contentContainer}>
|
|
59
59
|
{label && (
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
</GridCol>
|
|
82
|
-
</GridRow>
|
|
60
|
+
<>
|
|
61
|
+
<div className={styles.labelContainer}>
|
|
62
|
+
<span
|
|
63
|
+
className={classNames(styles.labelText, {
|
|
64
|
+
[styles.labelWordWrap]: isHeadingWordWrap,
|
|
65
|
+
})}
|
|
66
|
+
>
|
|
67
|
+
{label.text}
|
|
68
|
+
</span>
|
|
69
|
+
<div className={styles.labelDate}>
|
|
70
|
+
<Text size="xs" tone="muted">
|
|
71
|
+
{isDate(label.date) ? (
|
|
72
|
+
<time dateTime={format(label.date as Date, "yyyy-MM-dd")}>
|
|
73
|
+
{format(label.date as Date, "d MMMM yyyy")}
|
|
74
|
+
</time>
|
|
75
|
+
) : (
|
|
76
|
+
<span>{label.date as string}</span>
|
|
77
|
+
)}
|
|
78
|
+
</Text>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
83
81
|
<VerticalSpace size="24" />
|
|
84
|
-
|
|
82
|
+
</>
|
|
85
83
|
)}
|
|
86
84
|
|
|
87
85
|
<Heading
|
|
@@ -119,7 +117,10 @@ export const FeaturePost: React.FC<FeaturePostProps> = ({
|
|
|
119
117
|
)}
|
|
120
118
|
<VerticalSpace size="40" />
|
|
121
119
|
|
|
122
|
-
<ButtonV2
|
|
120
|
+
<ButtonV2
|
|
121
|
+
{...footer.button}
|
|
122
|
+
buttonStyle={footer.button.buttonStyle ?? "secondary"}
|
|
123
|
+
/>
|
|
123
124
|
</>
|
|
124
125
|
)}
|
|
125
126
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ButtonV2Props } from "@arc-ui/components/ButtonV2";
|
|
2
2
|
|
|
3
3
|
import { type AuthorProps } from "../../Author/Author";
|
|
4
4
|
|
|
5
5
|
export interface FeaturePostCtaFooter {
|
|
6
6
|
type: "cta";
|
|
7
|
+
/** CTA button options. The button style defaults to `secondary`. */
|
|
7
8
|
button: Pick<
|
|
8
|
-
|
|
9
|
-
"label" | "onClick" | "href" | "target" | "ariaLabel"
|
|
9
|
+
ButtonV2Props,
|
|
10
|
+
"label" | "onClick" | "href" | "target" | "ariaLabel" | "buttonStyle"
|
|
10
11
|
>;
|
|
11
12
|
author?: AuthorProps;
|
|
12
13
|
}
|
|
@@ -50,22 +50,18 @@ export const Highlights: React.FC<HighlightsProps> = ({
|
|
|
50
50
|
<>
|
|
51
51
|
<VerticalSpace size="24" />
|
|
52
52
|
<ButtonV2
|
|
53
|
-
|
|
54
|
-
href={cta.href}
|
|
55
|
-
onClick={cta.onClick}
|
|
56
|
-
target={cta.target}
|
|
53
|
+
{...cta}
|
|
57
54
|
size="m"
|
|
58
|
-
|
|
59
|
-
buttonStyle={cta.buttonStyle}
|
|
55
|
+
buttonStyle={cta.buttonStyle ?? "secondary"}
|
|
60
56
|
icon={
|
|
61
57
|
cta.buttonStyle === "compact"
|
|
62
58
|
? (cta.icon ?? BtIconArrowRightFill)
|
|
63
|
-
:
|
|
59
|
+
: cta.icon
|
|
64
60
|
}
|
|
65
61
|
iconActive={
|
|
66
62
|
cta.buttonStyle === "compact"
|
|
67
63
|
? (cta.iconActive ?? BtIconArrowRightFill)
|
|
68
|
-
:
|
|
64
|
+
: cta.iconActive
|
|
69
65
|
}
|
|
70
66
|
iconPosition={cta.iconPosition ?? "beforeText"}
|
|
71
67
|
/>
|
|
@@ -91,28 +87,5 @@ export interface HighlightsProps extends Omit<SectionHeadingProps, "isPadded"> {
|
|
|
91
87
|
* Optional CTA rendered at the end of Highlights.
|
|
92
88
|
* Only ONE CTA is allowed.
|
|
93
89
|
*/
|
|
94
|
-
cta?:
|
|
90
|
+
cta?: ButtonV2Props;
|
|
95
91
|
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* CTA configuration for Highlights
|
|
99
|
-
*/
|
|
100
|
-
export type HighlightsCTAProps = Pick<
|
|
101
|
-
ButtonV2Props,
|
|
102
|
-
"label" | "href" | "onClick" | "iconPosition" | "ariaLabel" | "target"
|
|
103
|
-
> & {
|
|
104
|
-
/**
|
|
105
|
-
* Allowed button styles only.
|
|
106
|
-
* Primary is intentionally NOT supported.
|
|
107
|
-
*/
|
|
108
|
-
buttonStyle: "secondary" | "compact";
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Icon override & Active icon override
|
|
112
|
-
* - undefined → uses default BtIconArrowRightFill for compact style
|
|
113
|
-
* - null → icon disabled
|
|
114
|
-
* - string → custom icon
|
|
115
|
-
*/
|
|
116
|
-
icon?: string | null;
|
|
117
|
-
iconActive?: string | null;
|
|
118
|
-
};
|
|
@@ -75,39 +75,39 @@ export const ProductCard: React.FC<ProductCardProps> = ({
|
|
|
75
75
|
|
|
76
76
|
export interface ProductCardProps {
|
|
77
77
|
/*
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
* Product heading displayed at the top of the card
|
|
79
|
+
*/
|
|
80
80
|
heading: string;
|
|
81
81
|
|
|
82
82
|
/*
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
* Semantic heading level (e.g. "2", "3") for accessibility
|
|
84
|
+
*/
|
|
85
85
|
headingLevel: HeadingLevel;
|
|
86
86
|
|
|
87
87
|
/*
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
* Content rendered in the top section below the heading (e.g. ProductCardPrice, Text, ProductCardPriceIncrease)
|
|
89
|
+
*/
|
|
90
90
|
topSlot?: ReactNode;
|
|
91
91
|
|
|
92
92
|
/*
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
* Content rendered in the bottom section of the card (e.g. ProductCardBenefits)
|
|
94
|
+
*/
|
|
95
95
|
bottomSlot?: ReactNode;
|
|
96
96
|
|
|
97
97
|
/*
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
* Optional promotional content displayed as a banner above the card. Renders nothing when not provided.
|
|
99
|
+
*/
|
|
100
100
|
promoted?: {
|
|
101
101
|
content: ReactNode;
|
|
102
102
|
background: SurfaceBackgroundColor;
|
|
103
103
|
};
|
|
104
104
|
/*
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
* Whether to show a horizontal rule divider between the top and bottom sections of the card
|
|
106
|
+
*/
|
|
107
107
|
divider?: boolean;
|
|
108
108
|
|
|
109
109
|
/*
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
* Configuration for the card's call-to-action button. Accepts all ButtonV2 props except `buttonStyle` and `isFullWidth`, which are set internally.
|
|
111
|
+
*/
|
|
112
112
|
button?: Omit<ButtonV2Props, "buttonStyle" | "isFullWidth">;
|
|
113
113
|
}
|
|
@@ -15,9 +15,17 @@ export const ProductCardPrice: React.FC<ProductCardPriceProps> = ({
|
|
|
15
15
|
{prefix}
|
|
16
16
|
</Text>
|
|
17
17
|
)}
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
<div style={{ display: "flex", alignItems: "baseline" }}>
|
|
19
|
+
<Heading color="brand" size="s">
|
|
20
|
+
{value.slice(0, 1)}
|
|
21
|
+
</Heading>
|
|
22
|
+
<Heading color="brand" size="l">
|
|
23
|
+
{value.slice(1, -3)}
|
|
24
|
+
</Heading>
|
|
25
|
+
<Heading color="brand" size="s">
|
|
26
|
+
{value.slice(-3)}
|
|
27
|
+
</Heading>
|
|
28
|
+
</div>
|
|
21
29
|
{suffix && (
|
|
22
30
|
<Text tone="muted" size="s">
|
|
23
31
|
{suffix}
|
|
@@ -5,7 +5,12 @@ import { VerticalSpace } from "@arc-ui/components/VerticalSpace";
|
|
|
5
5
|
|
|
6
6
|
export const ProductCardPriceIncrease: React.FC<
|
|
7
7
|
ProductCardPriceIncreaseProps
|
|
8
|
-
> = ({
|
|
8
|
+
> = ({
|
|
9
|
+
heading = "Price increasing to",
|
|
10
|
+
headingLevel = "4",
|
|
11
|
+
items = [],
|
|
12
|
+
hideBorder = false,
|
|
13
|
+
}) => {
|
|
9
14
|
return (
|
|
10
15
|
<>
|
|
11
16
|
<VerticalSpace size="16" />
|
|
@@ -14,11 +19,14 @@ export const ProductCardPriceIncrease: React.FC<
|
|
|
14
19
|
</Heading>
|
|
15
20
|
<VerticalSpace size="12" />
|
|
16
21
|
<div className={styles.priceIncreaseWrapper}>
|
|
17
|
-
<div className={styles.priceIncreaseBorder} />
|
|
22
|
+
{!hideBorder && <div className={styles.priceIncreaseBorder} />}
|
|
18
23
|
<ul className={styles.priceIncreaseList}>
|
|
19
24
|
{items.map((item, index) => (
|
|
20
25
|
<li key={index} className={styles.priceIncreaseItem}>
|
|
21
|
-
|
|
26
|
+
<>
|
|
27
|
+
<b>{item.slice(0, 6)}</b>
|
|
28
|
+
<>{item.slice(6)}</>
|
|
29
|
+
</>
|
|
22
30
|
</li>
|
|
23
31
|
))}
|
|
24
32
|
</ul>
|
|
@@ -36,4 +44,7 @@ export interface ProductCardPriceIncreaseProps {
|
|
|
36
44
|
|
|
37
45
|
/** List of price increase entries (e.g. ["£48.99 by April 2027"]) */
|
|
38
46
|
items: string[];
|
|
47
|
+
|
|
48
|
+
/** Optional prop to hide the left border */
|
|
49
|
+
hideBorder?: boolean;
|
|
39
50
|
}
|