@flamingo-stack/openframe-frontend-core 0.0.596-2034.5684.1 → 0.0.596-2034.5686.1
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/dist/__tests__/fixtures/meeting-booking.d.ts +21 -0
- package/dist/__tests__/fixtures/meeting-booking.d.ts.map +1 -1
- package/dist/{chunk-NE3SHUIJ.cjs → chunk-25LZZY6E.cjs} +3 -3
- package/dist/{chunk-NE3SHUIJ.cjs.map → chunk-25LZZY6E.cjs.map} +1 -1
- package/dist/{chunk-CBR4H6JJ.cjs → chunk-3IT35YDV.cjs} +53 -53
- package/dist/{chunk-CBR4H6JJ.cjs.map → chunk-3IT35YDV.cjs.map} +1 -1
- package/dist/{chunk-WTK5YZYU.js → chunk-A6YERC4N.js} +2 -2
- package/dist/{chunk-XNZMKCA4.js → chunk-AGKFT4FA.js} +5 -5
- package/dist/{chunk-QAJXIOK2.js → chunk-AYMBMVOD.js} +2 -2
- package/dist/{chunk-7XHONLYQ.js → chunk-BLYQD3PG.js} +2 -2
- package/dist/{chunk-WDOERQZK.cjs → chunk-D3Q3VYRU.cjs} +16 -16
- package/dist/{chunk-WDOERQZK.cjs.map → chunk-D3Q3VYRU.cjs.map} +1 -1
- package/dist/{chunk-DD64DQHM.cjs → chunk-E4YWJY3A.cjs} +4 -4
- package/dist/{chunk-DD64DQHM.cjs.map → chunk-E4YWJY3A.cjs.map} +1 -1
- package/dist/{chunk-E7FDTOJ6.js → chunk-FGHJ3BC7.js} +3 -3
- package/dist/{chunk-E6NN7MAN.js → chunk-FIQIT6HX.js} +2 -2
- package/dist/{chunk-J2OCCRFC.js → chunk-GZ76RASF.js} +4 -4
- package/dist/{chunk-BC73KAXX.cjs → chunk-HPUD7X67.cjs} +4 -4
- package/dist/{chunk-BC73KAXX.cjs.map → chunk-HPUD7X67.cjs.map} +1 -1
- package/dist/{chunk-B3446RB6.cjs → chunk-IDDGXYES.cjs} +17 -17
- package/dist/{chunk-B3446RB6.cjs.map → chunk-IDDGXYES.cjs.map} +1 -1
- package/dist/{chunk-ESHCKTES.js → chunk-J5EA7PMY.js} +4 -4
- package/dist/{chunk-3PUXHFC4.js → chunk-JVYNMMNE.js} +3 -3
- package/dist/{chunk-QT6Q6SUK.js → chunk-KOLVBEW5.js} +1 -1
- package/dist/{chunk-QT6Q6SUK.js.map → chunk-KOLVBEW5.js.map} +1 -1
- package/dist/{chunk-NBJELHZE.cjs → chunk-MN4TFBRA.cjs} +3 -3
- package/dist/{chunk-NBJELHZE.cjs.map → chunk-MN4TFBRA.cjs.map} +1 -1
- package/dist/{chunk-C2UBAAGD.cjs → chunk-N6HNK25A.cjs} +1 -1
- package/dist/{chunk-C2UBAAGD.cjs.map → chunk-N6HNK25A.cjs.map} +1 -1
- package/dist/{chunk-NTJGRZ7G.cjs → chunk-OI7RNQAS.cjs} +33 -33
- package/dist/{chunk-NTJGRZ7G.cjs.map → chunk-OI7RNQAS.cjs.map} +1 -1
- package/dist/{chunk-OKCGXXEK.js → chunk-PO4ATWE4.js} +2 -2
- package/dist/{chunk-X6TLDYMG.js → chunk-QNZM43HU.js} +3 -3
- package/dist/{chunk-6RISTRR3.cjs → chunk-QVOGALQE.cjs} +3 -3
- package/dist/{chunk-6RISTRR3.cjs.map → chunk-QVOGALQE.cjs.map} +1 -1
- package/dist/{chunk-IJ4SGRAN.js → chunk-S7LV66HD.js} +6 -6
- package/dist/{chunk-6X63WX5D.cjs → chunk-SLIUM3VO.cjs} +34 -34
- package/dist/{chunk-6X63WX5D.cjs.map → chunk-SLIUM3VO.cjs.map} +1 -1
- package/dist/{chunk-XTZECFMT.cjs → chunk-TDLETTI4.cjs} +10 -10
- package/dist/{chunk-XTZECFMT.cjs.map → chunk-TDLETTI4.cjs.map} +1 -1
- package/dist/{chunk-S2WP7ELW.js → chunk-TYURDSIN.js} +34 -32
- package/dist/chunk-TYURDSIN.js.map +1 -0
- package/dist/{chunk-DR4WXTKP.cjs → chunk-TZK5N6AH.cjs} +83 -81
- package/dist/chunk-TZK5N6AH.cjs.map +1 -0
- package/dist/{chunk-EM3T7YXE.js → chunk-Y6JB7X5Y.js} +2 -2
- package/dist/{chunk-HCHTKE6I.cjs → chunk-YP56SX7C.cjs} +3 -3
- package/dist/{chunk-HCHTKE6I.cjs.map → chunk-YP56SX7C.cjs.map} +1 -1
- package/dist/{chunk-UG43WQ2C.cjs → chunk-YQYOEGAT.cjs} +23 -23
- package/dist/{chunk-UG43WQ2C.cjs.map → chunk-YQYOEGAT.cjs.map} +1 -1
- package/dist/{chunk-YZALGXOM.js → chunk-ZVBYWM5Z.js} +2 -2
- package/dist/{chunk-ANGSMT5Q.cjs → chunk-ZXEYS2XP.cjs} +17 -17
- package/dist/{chunk-ANGSMT5Q.cjs.map → chunk-ZXEYS2XP.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +35 -33
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +15 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/booking-form.d.ts +15 -3
- package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +81 -89
- package/dist/components/meeting-scheduler/index.cjs.map +1 -1
- package/dist/components/meeting-scheduler/index.d.ts +2 -0
- package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
- package/dist/components/meeting-scheduler/index.js +56 -64
- package/dist/components/meeting-scheduler/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/benefit-card.d.ts +7 -0
- package/dist/components/ui/benefit-card.d.ts.map +1 -1
- package/dist/components/ui/field-wrapper.d.ts +4 -4
- package/dist/components/ui/file-manager/index.cjs +4 -4
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +5 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +4 -2
- package/dist/index.cjs +9 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/schemas/meeting-booking-schema.cjs.map +1 -1
- package/dist/schemas/meeting-booking-schema.d.ts.map +1 -1
- package/dist/schemas/meeting-booking-schema.js.map +1 -1
- package/dist/utils/hubspot-meetings-convention.d.ts +1 -1
- package/dist/utils/hubspot-meetings-convention.d.ts.map +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/benefit-card-border.test.ts +29 -0
- package/src/__tests__/booking-form-field-rows.test.tsx +9 -47
- package/src/__tests__/booking-form-number-field.test.tsx +14 -44
- package/src/__tests__/fixtures/meeting-booking.tsx +61 -0
- package/src/__tests__/meeting-scheduler-details-first.test.tsx +3 -6
- package/src/components/meeting-scheduler/booking-form.tsx +96 -70
- package/src/components/meeting-scheduler/index.tsx +24 -14
- package/src/components/ui/benefit-card.tsx +45 -39
- package/src/components/ui/field-wrapper.tsx +4 -4
- package/src/schemas/meeting-booking-schema.ts +1 -0
- package/src/utils/hubspot-meetings-convention.ts +1 -1
- package/dist/chunk-DR4WXTKP.cjs.map +0 -1
- package/dist/chunk-S2WP7ELW.js.map +0 -1
- package/src/__tests__/fixtures/meeting-booking.ts +0 -27
- /package/dist/{chunk-WTK5YZYU.js.map → chunk-A6YERC4N.js.map} +0 -0
- /package/dist/{chunk-XNZMKCA4.js.map → chunk-AGKFT4FA.js.map} +0 -0
- /package/dist/{chunk-QAJXIOK2.js.map → chunk-AYMBMVOD.js.map} +0 -0
- /package/dist/{chunk-7XHONLYQ.js.map → chunk-BLYQD3PG.js.map} +0 -0
- /package/dist/{chunk-E7FDTOJ6.js.map → chunk-FGHJ3BC7.js.map} +0 -0
- /package/dist/{chunk-E6NN7MAN.js.map → chunk-FIQIT6HX.js.map} +0 -0
- /package/dist/{chunk-J2OCCRFC.js.map → chunk-GZ76RASF.js.map} +0 -0
- /package/dist/{chunk-ESHCKTES.js.map → chunk-J5EA7PMY.js.map} +0 -0
- /package/dist/{chunk-3PUXHFC4.js.map → chunk-JVYNMMNE.js.map} +0 -0
- /package/dist/{chunk-OKCGXXEK.js.map → chunk-PO4ATWE4.js.map} +0 -0
- /package/dist/{chunk-X6TLDYMG.js.map → chunk-QNZM43HU.js.map} +0 -0
- /package/dist/{chunk-IJ4SGRAN.js.map → chunk-S7LV66HD.js.map} +0 -0
- /package/dist/{chunk-EM3T7YXE.js.map → chunk-Y6JB7X5Y.js.map} +0 -0
- /package/dist/{chunk-YZALGXOM.js.map → chunk-ZVBYWM5Z.js.map} +0 -0
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
import { cn } from '../../utils/cn';
|
|
56
56
|
import { formatDurationCompact } from '../../utils/format';
|
|
57
57
|
import { Alert, AlertDescription, Button } from '../ui';
|
|
58
|
-
import { BookingForm, BookingFormSkeleton, type BookingFormProps } from './booking-form';
|
|
58
|
+
import { BookingForm, BookingFormSkeleton, DEFAULT_SUBMIT_LABEL, type BookingFormProps } from './booking-form';
|
|
59
59
|
import { Confirmation } from './confirmation';
|
|
60
60
|
import { SchedulerContextPanel, ContextPanelSkeleton } from './context-panel';
|
|
61
61
|
import { SlotPicker, SlotPickerSkeleton, dayKeyInZone } from './slot-picker';
|
|
@@ -186,6 +186,8 @@ const ACTION_PANEL_CLASS = 'flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--
|
|
|
186
186
|
* being cut off at the card's edge.
|
|
187
187
|
*/
|
|
188
188
|
const PANEL_STEP_CLASS = 'p-[var(--spacing-system-l)] md:min-h-0 md:overflow-y-auto lg:p-0';
|
|
189
|
+
/** A step's panel: the inset above on a column that fills the action side. */
|
|
190
|
+
const STEP_PANEL_CLASS = cn('flex flex-1 flex-col', PANEL_STEP_CLASS);
|
|
189
191
|
|
|
190
192
|
/**
|
|
191
193
|
* The widget's height on desktop: a FIXED 380px, and the ONLY size the whole
|
|
@@ -255,6 +257,9 @@ export const MEETING_SCHEDULER_DETAILS_FIRST_H = 'md:h-[39.875rem]';
|
|
|
255
257
|
|
|
256
258
|
export type SchedulerFlow = 'slot-first' | 'details-first';
|
|
257
259
|
|
|
260
|
+
/** The flow every existing embed gets — the ONE spelling of the default. */
|
|
261
|
+
export const DEFAULT_SCHEDULER_FLOW: SchedulerFlow = 'slot-first';
|
|
262
|
+
|
|
258
263
|
/**
|
|
259
264
|
* What differs between the two flows as DATA — first step, the box a host
|
|
260
265
|
* reserves, the form's submit copy. Exported so a host that swaps the card in
|
|
@@ -266,7 +271,7 @@ export const SCHEDULER_FLOW_PRESETS: Record<
|
|
|
266
271
|
SchedulerFlow,
|
|
267
272
|
{ initialStep: Exclude<Step, 'confirmed'>; height: string; submitLabel: string; footerNote?: string }
|
|
268
273
|
> = {
|
|
269
|
-
'slot-first': { initialStep: 'slot', height: MEETING_SCHEDULER_H, submitLabel:
|
|
274
|
+
'slot-first': { initialStep: 'slot', height: MEETING_SCHEDULER_H, submitLabel: DEFAULT_SUBMIT_LABEL },
|
|
270
275
|
'details-first': {
|
|
271
276
|
initialStep: 'details',
|
|
272
277
|
height: MEETING_SCHEDULER_DETAILS_FIRST_H,
|
|
@@ -334,7 +339,7 @@ const CARD_DEGRADED_CLASS = cn(
|
|
|
334
339
|
* from {@link SCHEDULER_FLOW_PRESETS}, so the pairing is never re-derived.
|
|
335
340
|
*/
|
|
336
341
|
export function SchedulerDegradedCard({
|
|
337
|
-
flow =
|
|
342
|
+
flow = DEFAULT_SCHEDULER_FLOW,
|
|
338
343
|
className,
|
|
339
344
|
message,
|
|
340
345
|
action,
|
|
@@ -385,7 +390,7 @@ export function HubSpotMeetingScheduler({
|
|
|
385
390
|
onBack,
|
|
386
391
|
onBooked,
|
|
387
392
|
className,
|
|
388
|
-
flow =
|
|
393
|
+
flow = DEFAULT_SCHEDULER_FLOW,
|
|
389
394
|
detailsForm: DetailsForm = BookingForm,
|
|
390
395
|
detailsFormProps,
|
|
391
396
|
}: HubSpotMeetingSchedulerProps) {
|
|
@@ -665,6 +670,11 @@ export function HubSpotMeetingScheduler({
|
|
|
665
670
|
* last-wins, so appending it after would make a host's own `h-*` unreachable,
|
|
666
671
|
* which is the override the height-inside-CARD_CLASS arrangement allows today.
|
|
667
672
|
*/
|
|
673
|
+
/** The card's ONE back edge, per flow and step — the skeleton and the loaded
|
|
674
|
+
* panel wire the same one, so a month load can never swap it for the host's
|
|
675
|
+
* exit under a details-first visitor. */
|
|
676
|
+
const backEdge = detailsFirst ? (step === 'slot' ? backToDetails : onBack) : step === 'details' ? backToSlot : onBack;
|
|
677
|
+
|
|
668
678
|
const cardClass = cn(CARD_CLASS, preset.height, formOnly && 'bg-ods-bg', className);
|
|
669
679
|
|
|
670
680
|
/** One card SHAPE for every degraded return below. */
|
|
@@ -681,7 +691,7 @@ export function HubSpotMeetingScheduler({
|
|
|
681
691
|
return (
|
|
682
692
|
<div className={cardClass}>
|
|
683
693
|
<div className={CARD_INNER_CLASS}>
|
|
684
|
-
{!formOnly && <ContextPanelSkeleton onBack={
|
|
694
|
+
{!formOnly && <ContextPanelSkeleton onBack={backEdge} className={CONTEXT_PANEL_CLASS} />}
|
|
685
695
|
<div className={ACTION_PANEL_CLASS}>
|
|
686
696
|
{/* Both terms matter. Without `flow` this stays a calendar where
|
|
687
697
|
the form belongs; without the step term, paging a month AFTER
|
|
@@ -690,8 +700,8 @@ export function HubSpotMeetingScheduler({
|
|
|
690
700
|
{detailsFirst && step === 'details' ? (
|
|
691
701
|
// The SAME wrapper the loaded form gets, so the skeleton never
|
|
692
702
|
// states its own inset.
|
|
693
|
-
<div className={
|
|
694
|
-
<BookingFormSkeleton fieldRows={detailsFormProps?.fieldRows} />
|
|
703
|
+
<div className={STEP_PANEL_CLASS}>
|
|
704
|
+
<BookingFormSkeleton fieldRows={detailsFormProps?.fieldRows} footerNote={preset.footerNote} />
|
|
695
705
|
</div>
|
|
696
706
|
) : (
|
|
697
707
|
<SlotPickerSkeleton monthOffset={monthOffset} />
|
|
@@ -742,9 +752,7 @@ export function HubSpotMeetingScheduler({
|
|
|
742
752
|
// details-first inverts the mapping: Back at `slot` returns to the
|
|
743
753
|
// form. At `details` the form-only layout mounts no panel, so a host
|
|
744
754
|
// exit is not rendered there (the page around the card is the exit).
|
|
745
|
-
onBack={
|
|
746
|
-
detailsFirst ? (step === 'slot' ? backToDetails : onBack) : step === 'details' ? backToSlot : onBack
|
|
747
|
-
}
|
|
755
|
+
onBack={backEdge}
|
|
748
756
|
// `locked` HIDES the selectors (post-selection steps); `disabled`
|
|
749
757
|
// keeps them mounted but inert. details-first needs the second while
|
|
750
758
|
// a POST is in flight: a mid-flight change would clear the slot
|
|
@@ -765,17 +773,19 @@ export function HubSpotMeetingScheduler({
|
|
|
765
773
|
default `min-height:auto` refuses to shrink below its content. */}
|
|
766
774
|
<div className={ACTION_PANEL_CLASS}>
|
|
767
775
|
{step === 'confirmed' && confirmation && timezone ? (
|
|
768
|
-
<div className={
|
|
776
|
+
<div className={STEP_PANEL_CLASS}>
|
|
769
777
|
<Confirmation confirmation={confirmation} timezone={timezone} />
|
|
770
778
|
</div>
|
|
771
779
|
) : step === 'details' &&
|
|
772
780
|
(detailsFirst
|
|
773
781
|
? // The form is step ONE here: no slot yet, and `timezone` is null
|
|
774
782
|
// on the server render, so neither may gate the PANEL. A link
|
|
775
|
-
// publishing no durations has no form worth showing
|
|
776
|
-
|
|
783
|
+
// publishing no durations has no form worth showing — but while a
|
|
784
|
+
// refetch is in flight the form stays (a calendar skeleton is
|
|
785
|
+
// the wrong shape here).
|
|
786
|
+
durations.length > 0 || isFetchingAvailability
|
|
777
787
|
: durationMs != null && selectedSlot != null && timezone) ? (
|
|
778
|
-
<div className={cn('
|
|
788
|
+
<div className={cn(STEP_PANEL_CLASS, 'gap-[var(--spacing-system-m)]')}>
|
|
779
789
|
{/* Top-aligned, and no back edge of its own: the ONE back edge
|
|
780
790
|
lives in the context panel at every step (the `onBack` wired on `SchedulerContextPanel`),
|
|
781
791
|
which is where the design puts it and the only spot that
|
|
@@ -61,6 +61,50 @@ interface BenefitCardGridProps {
|
|
|
61
61
|
columns?: 2 | 3 | 4; // Support 2, 3, or 4 columns
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Which dividers card `index` of `totalItems` draws in a `columns` grid. Pure:
|
|
66
|
+
* the per-breakpoint row math (a 3-column grid lays out 1-up / 2-up / 3-up, so a
|
|
67
|
+
* card's row differs per breakpoint) is what needs testing, and the grid itself
|
|
68
|
+
* is a `cloneElement` over children.
|
|
69
|
+
*/
|
|
70
|
+
export function benefitCardBorderClass(index: number, totalItems: number, columns: number): string {
|
|
71
|
+
const isLastItem = index === totalItems - 1;
|
|
72
|
+
if (columns === 4) {
|
|
73
|
+
// 4-column grid borders
|
|
74
|
+
const isLastInRow = (index + 1) % 4 === 0;
|
|
75
|
+
const isInLastRow = index >= totalItems - 4;
|
|
76
|
+
return cn(
|
|
77
|
+
!isLastInRow && 'border-ods-border md:border-r',
|
|
78
|
+
!isInLastRow && 'border-b md:border-b lg:border-b-0',
|
|
79
|
+
index < 2 && 'lg:border-b-0',
|
|
80
|
+
);
|
|
81
|
+
} else if (columns === 3) {
|
|
82
|
+
// 3 columns lay out 1-up / 2-up / 3-up, so the row a card sits in
|
|
83
|
+
// differs per breakpoint and a single rule cannot describe it. The
|
|
84
|
+
// old shared 2-3 branch assumed one row and, at `md` (grid-cols-2),
|
|
85
|
+
// gave card 1 a trailing `md:border-r` while suppressing the divider
|
|
86
|
+
// between the two rows.
|
|
87
|
+
const isLastInMdRow = (index + 1) % 2 === 0;
|
|
88
|
+
const isInLastMdRow = index >= totalItems - (totalItems % 2 === 0 ? 2 : 1);
|
|
89
|
+
const isLastInLgRow = (index + 1) % 3 === 0;
|
|
90
|
+
const isInLastLgRow = index >= totalItems - (totalItems % 3 === 0 ? 3 : totalItems % 3);
|
|
91
|
+
return cn(
|
|
92
|
+
'border-ods-border',
|
|
93
|
+
!isLastItem && 'border-b',
|
|
94
|
+
isInLastMdRow && 'md:border-b-0',
|
|
95
|
+
!isLastInMdRow && !isLastItem && 'md:border-r',
|
|
96
|
+
isLastInMdRow && 'md:border-r-0',
|
|
97
|
+
isInLastLgRow && 'lg:border-b-0',
|
|
98
|
+
!isInLastMdRow && !isInLastLgRow && 'lg:border-b',
|
|
99
|
+
!isLastInLgRow && !isLastItem && 'lg:border-r',
|
|
100
|
+
isLastInLgRow && 'lg:border-r-0',
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
// 2 columns: a flex row, so one rule genuinely does describe it.
|
|
104
|
+
return isLastItem ? 'border-b-0' : 'border-b md:border-b-0 md:border-r border-ods-border';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
64
108
|
export const BenefitCardGrid: React.FC<BenefitCardGridProps> = ({ children, className = '', columns = 2 }) => {
|
|
65
109
|
const childrenArray = React.Children.toArray(children);
|
|
66
110
|
|
|
@@ -80,45 +124,7 @@ export const BenefitCardGrid: React.FC<BenefitCardGridProps> = ({ children, clas
|
|
|
80
124
|
)}
|
|
81
125
|
>
|
|
82
126
|
{childrenArray.map((child, index) => {
|
|
83
|
-
const
|
|
84
|
-
const isLastItem = index === totalItems - 1;
|
|
85
|
-
|
|
86
|
-
// Dynamic border logic based on columns
|
|
87
|
-
let borderClass = '';
|
|
88
|
-
if (columns === 4) {
|
|
89
|
-
// 4-column grid borders
|
|
90
|
-
const isLastInRow = (index + 1) % 4 === 0;
|
|
91
|
-
const isInLastRow = index >= totalItems - 4;
|
|
92
|
-
borderClass = cn(
|
|
93
|
-
!isLastInRow && 'border-ods-border md:border-r',
|
|
94
|
-
!isInLastRow && 'border-b md:border-b lg:border-b-0',
|
|
95
|
-
index < 2 && 'lg:border-b-0',
|
|
96
|
-
);
|
|
97
|
-
} else if (columns === 3) {
|
|
98
|
-
// 3 columns lay out 1-up / 2-up / 3-up, so the row a card sits in
|
|
99
|
-
// differs per breakpoint and a single rule cannot describe it. The
|
|
100
|
-
// old shared 2-3 branch assumed one row and, at `md` (grid-cols-2),
|
|
101
|
-
// gave card 1 a trailing `md:border-r` while suppressing the divider
|
|
102
|
-
// between the two rows.
|
|
103
|
-
const isLastInMdRow = (index + 1) % 2 === 0;
|
|
104
|
-
const isInLastMdRow = index >= totalItems - (totalItems % 2 === 0 ? 2 : 1);
|
|
105
|
-
const isLastInLgRow = (index + 1) % 3 === 0;
|
|
106
|
-
const isInLastLgRow = index >= totalItems - (totalItems % 3 === 0 ? 3 : totalItems % 3);
|
|
107
|
-
borderClass = cn(
|
|
108
|
-
'border-ods-border',
|
|
109
|
-
!isLastItem && 'border-b',
|
|
110
|
-
isInLastMdRow && 'md:border-b-0',
|
|
111
|
-
!isLastInMdRow && !isLastItem && 'md:border-r',
|
|
112
|
-
isLastInMdRow && 'md:border-r-0',
|
|
113
|
-
isInLastLgRow && 'lg:border-b-0',
|
|
114
|
-
!isInLastMdRow && !isInLastLgRow && 'lg:border-b',
|
|
115
|
-
!isLastInLgRow && !isLastItem && 'lg:border-r',
|
|
116
|
-
isLastInLgRow && 'lg:border-r-0',
|
|
117
|
-
);
|
|
118
|
-
} else {
|
|
119
|
-
// 2 columns: a flex row, so one rule genuinely does describe it.
|
|
120
|
-
borderClass = isLastItem ? 'border-b-0' : 'border-b md:border-b-0 md:border-r border-ods-border';
|
|
121
|
-
}
|
|
127
|
+
const borderClass = benefitCardBorderClass(index, childrenArray.length, columns);
|
|
122
128
|
|
|
123
129
|
return React.cloneElement(child as React.ReactElement<{ className?: string }>, {
|
|
124
130
|
key: index,
|
|
@@ -36,10 +36,10 @@ export interface FieldWrapperProps {
|
|
|
36
36
|
*/
|
|
37
37
|
labelVariant?: 'default' | 'large';
|
|
38
38
|
/**
|
|
39
|
-
* Marks the label with the `RequiredMark` `ContactForm` draws on its
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `
|
|
39
|
+
* Marks the label with the `RequiredMark` `ContactForm` draws on its required
|
|
40
|
+
* fields, so a form mixing both never shows two conventions. Visual only — the
|
|
41
|
+
* CONTROL carries `required`/`aria-required` for assistive tech; the star is
|
|
42
|
+
* `aria-hidden`.
|
|
43
43
|
*/
|
|
44
44
|
required?: boolean;
|
|
45
45
|
/** Additional className for the outer wrapper */
|
|
@@ -32,12 +32,12 @@ export const SCHEDULING_SLUG_SHAPE = /^[a-z0-9-]+(\/[a-z0-9-]+)*$/;
|
|
|
32
32
|
/** Upper bound for availability month paging (widget nav, route 400, DAL clamp). */
|
|
33
33
|
export const MAX_MONTH_OFFSET = 11;
|
|
34
34
|
|
|
35
|
-
/** Full-slug shape validity (path-resolution safety only — never semantics). */
|
|
36
35
|
/** The one spelling of slug normalisation — every map key and lookup goes through it. */
|
|
37
36
|
export function normalizeSchedulingSlug(slug: string): string {
|
|
38
37
|
return slug.trim().toLowerCase();
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
/** Full-slug shape validity (path-resolution safety only — never semantics). */
|
|
41
41
|
export function isValidSchedulingSlug(slug: string): boolean {
|
|
42
42
|
return SCHEDULING_SLUG_SHAPE.test(normalizeSchedulingSlug(slug));
|
|
43
43
|
}
|