@flamingo-stack/openframe-frontend-core 0.0.577 → 0.0.578-snapshot.20260831161155
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/{chunk-XBSW7IF6.cjs → chunk-36TJYCF3.cjs} +27 -27
- package/dist/{chunk-XBSW7IF6.cjs.map → chunk-36TJYCF3.cjs.map} +1 -1
- package/dist/{chunk-ET3Q4FIZ.cjs → chunk-37XJYOAY.cjs} +17 -17
- package/dist/{chunk-ET3Q4FIZ.cjs.map → chunk-37XJYOAY.cjs.map} +1 -1
- package/dist/{chunk-4D2BR756.js → chunk-3AX3ZEY3.js} +53 -32
- package/dist/chunk-3AX3ZEY3.js.map +1 -0
- package/dist/{chunk-JQI726T3.js → chunk-3LMYQGAJ.js} +3 -2
- package/dist/chunk-3LMYQGAJ.js.map +1 -0
- package/dist/{chunk-XBBCUC32.cjs → chunk-4FJSN2XW.cjs} +15 -15
- package/dist/{chunk-XBBCUC32.cjs.map → chunk-4FJSN2XW.cjs.map} +1 -1
- package/dist/{chunk-RCLJSENK.cjs → chunk-4HGRXFQP.cjs} +6 -6
- package/dist/{chunk-RCLJSENK.cjs.map → chunk-4HGRXFQP.cjs.map} +1 -1
- package/dist/{chunk-NQCGDZJ3.cjs → chunk-4LZ7B5H5.cjs} +5 -5
- package/dist/{chunk-NQCGDZJ3.cjs.map → chunk-4LZ7B5H5.cjs.map} +1 -1
- package/dist/{chunk-IQFXDS5O.js → chunk-4WSKSNXL.js} +10 -10
- package/dist/{chunk-NEKT67O5.js → chunk-5Y6TPFW7.js} +6 -6
- package/dist/{chunk-XG2WNF4L.cjs → chunk-A6C5HOD7.cjs} +27 -27
- package/dist/{chunk-XG2WNF4L.cjs.map → chunk-A6C5HOD7.cjs.map} +1 -1
- package/dist/{chunk-AQ5MZVYN.js → chunk-AKPLEWMH.js} +3 -3
- package/dist/{chunk-HSOMYCUG.cjs → chunk-BCJQ7CJE.cjs} +13 -13
- package/dist/{chunk-HSOMYCUG.cjs.map → chunk-BCJQ7CJE.cjs.map} +1 -1
- package/dist/{chunk-RUJKOU5F.js → chunk-BW4WDUSI.js} +3 -3
- package/dist/{chunk-NP3XN6ZX.js → chunk-CP5EYMNL.js} +278 -162
- package/dist/chunk-CP5EYMNL.js.map +1 -0
- package/dist/{chunk-FCVAVOXS.js → chunk-D44S2QLI.js} +3 -3
- package/dist/{chunk-U3SDSA5K.js → chunk-DB3TV4CW.js} +7 -7
- package/dist/{chunk-KE2TCUOH.cjs → chunk-FEVLXRTC.cjs} +537 -421
- package/dist/chunk-FEVLXRTC.cjs.map +1 -0
- package/dist/{chunk-ZBJ6XSDO.js → chunk-H4BPHGU5.js} +4 -4
- package/dist/{chunk-6FJABKPX.cjs → chunk-H54EPP3P.cjs} +9 -9
- package/dist/{chunk-6FJABKPX.cjs.map → chunk-H54EPP3P.cjs.map} +1 -1
- package/dist/{chunk-4KR7JWKB.js → chunk-HGA74X35.js} +5 -5
- package/dist/{chunk-5DPCIIJ7.cjs → chunk-HHJVIHVR.cjs} +92 -92
- package/dist/{chunk-5DPCIIJ7.cjs.map → chunk-HHJVIHVR.cjs.map} +1 -1
- package/dist/{chunk-WOHHZZJK.cjs → chunk-IFYQ7DHV.cjs} +62 -62
- package/dist/{chunk-WOHHZZJK.cjs.map → chunk-IFYQ7DHV.cjs.map} +1 -1
- package/dist/{chunk-QYQXAXVR.cjs → chunk-IKDC3R7V.cjs} +4 -3
- package/dist/chunk-IKDC3R7V.cjs.map +1 -0
- package/dist/{chunk-W6JA6SFD.js → chunk-JRYS4VMV.js} +45 -45
- package/dist/chunk-JRYS4VMV.js.map +1 -0
- package/dist/{chunk-2YCUEUIZ.js → chunk-L2CF2QZW.js} +8 -8
- package/dist/{chunk-2BFZHTSD.cjs → chunk-LEISUAHF.cjs} +18 -8
- package/dist/chunk-LEISUAHF.cjs.map +1 -0
- package/dist/{chunk-GDWBJFMG.cjs → chunk-M5GWCMYE.cjs} +42 -42
- package/dist/{chunk-GDWBJFMG.cjs.map → chunk-M5GWCMYE.cjs.map} +1 -1
- package/dist/{chunk-JCH6HGQD.js → chunk-MCWAZ5XI.js} +2 -2
- package/dist/{chunk-PQETCTJ4.js → chunk-MXOSTJAG.js} +4 -3
- package/dist/chunk-MXOSTJAG.js.map +1 -0
- package/dist/{chunk-MIY5FYX3.cjs → chunk-NNFQLTJ3.cjs} +4 -3
- package/dist/chunk-NNFQLTJ3.cjs.map +1 -0
- package/dist/{chunk-CFTZAZ7R.cjs → chunk-NPVDKOGD.cjs} +28 -28
- package/dist/{chunk-CFTZAZ7R.cjs.map → chunk-NPVDKOGD.cjs.map} +1 -1
- package/dist/{chunk-LYOGSVFO.js → chunk-O2FTVZOD.js} +5 -5
- package/dist/{chunk-CIUYHBAY.cjs → chunk-OGZAFM6M.cjs} +5 -5
- package/dist/{chunk-CIUYHBAY.cjs.map → chunk-OGZAFM6M.cjs.map} +1 -1
- package/dist/{chunk-THDGDYJ3.js → chunk-PMENIZXI.js} +7 -7
- package/dist/{chunk-TU32TSAK.js → chunk-PWOYEUIQ.js} +7 -7
- package/dist/{chunk-RSMUAAPW.js → chunk-QCP2KV7P.js} +5 -5
- package/dist/{chunk-RNUH5IHZ.js → chunk-QGJYWLF4.js} +3 -3
- package/dist/{chunk-75X6BRID.cjs → chunk-RKGOVTXZ.cjs} +500 -479
- package/dist/chunk-RKGOVTXZ.cjs.map +1 -0
- package/dist/{chunk-HM2R5MDR.js → chunk-T5HDPHN6.js} +13 -3
- package/dist/chunk-T5HDPHN6.js.map +1 -0
- package/dist/{chunk-GPWU3PPR.cjs → chunk-VEHZNQ77.cjs} +30 -30
- package/dist/{chunk-GPWU3PPR.cjs.map → chunk-VEHZNQ77.cjs.map} +1 -1
- package/dist/{chunk-APFGR2KK.cjs → chunk-YW4WBNYV.cjs} +41 -41
- package/dist/{chunk-APFGR2KK.cjs.map → chunk-YW4WBNYV.cjs.map} +1 -1
- package/dist/{chunk-36NFJULV.cjs → chunk-ZI27ALYX.cjs} +41 -41
- package/dist/chunk-ZI27ALYX.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +13 -13
- package/dist/components/case-studies/index.js +7 -7
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/chat/nav-link-anchor-via-runtime.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +9 -2
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chat-nav-resolution.d.ts +9 -3
- package/dist/components/chat/utils/chat-nav-resolution.d.ts.map +1 -1
- package/dist/components/chat/utils/execute-navigation.d.ts +11 -0
- package/dist/components/chat/utils/execute-navigation.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +1 -1
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/nav-anchor-props.d.ts +4 -0
- package/dist/components/chat/utils/nav-anchor-props.d.ts.map +1 -1
- package/dist/components/chat/utils/resolve-fetched-card-href.d.ts +42 -3
- package/dist/components/chat/utils/resolve-fetched-card-href.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +8 -8
- package/dist/components/contact/index.js +7 -7
- package/dist/components/docs/index.cjs +11 -11
- package/dist/components/docs/index.js +10 -10
- package/dist/components/embeds/index.cjs +8 -8
- package/dist/components/embeds/index.js +7 -7
- package/dist/components/faq/index.cjs +8 -8
- package/dist/components/faq/index.js +7 -7
- package/dist/components/features/board/board.d.ts.map +1 -1
- package/dist/components/features/index.cjs +9 -7
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +8 -6
- package/dist/components/features/sortable-list/index.d.ts +2 -1
- package/dist/components/features/sortable-list/index.d.ts.map +1 -1
- package/dist/components/features/sortable-list/sortable-list.d.ts +8 -1
- package/dist/components/features/sortable-list/sortable-list.d.ts.map +1 -1
- package/dist/components/features/sortable-list/sortable-move-buttons.d.ts +24 -0
- package/dist/components/features/sortable-list/sortable-move-buttons.d.ts.map +1 -0
- package/dist/components/features/sortable-list/use-sortable-item.d.ts +12 -4
- package/dist/components/features/sortable-list/use-sortable-item.d.ts.map +1 -1
- package/dist/components/features/ticket-status-config-list/ticket-status-config-list.d.ts +21 -5
- package/dist/components/features/ticket-status-config-list/ticket-status-config-list.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +27 -27
- package/dist/components/help-center-pages/index.js +18 -18
- package/dist/components/index.cjs +146 -144
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +20 -18
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +47 -47
- package/dist/components/meeting-scheduler/index.js +6 -6
- package/dist/components/navigation/index.cjs +10 -10
- package/dist/components/navigation/index.js +9 -9
- package/dist/components/onboarding-guides/index.cjs +10 -10
- package/dist/components/onboarding-guides/index.js +9 -9
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +11 -11
- package/dist/components/tickets/index.js +10 -10
- 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 +7 -7
- package/dist/components/ui/index.js +6 -6
- package/dist/components/ui/input.d.ts +2 -0
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts +2 -0
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/components/ui/ticket-status-config-row.d.ts +10 -4
- package/dist/components/ui/ticket-status-config-row.d.ts.map +1 -1
- package/dist/hooks/index.cjs +7 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +6 -2
- package/dist/hooks/ui/index.d.ts +1 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/use-drag-and-drop-enabled.d.ts +31 -0
- package/dist/hooks/ui/use-drag-and-drop-enabled.d.ts.map +1 -0
- package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
- package/dist/index.cjs +18 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/schemas/meeting-booking-schema.d.ts +2 -2
- package/dist/utils/content-href.d.ts +26 -5
- package/dist/utils/content-href.d.ts.map +1 -1
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -4
- package/src/components/chat/embeddable-chat.tsx +9 -2
- package/src/components/chat/entity-cards/.dispatch.md +1 -1
- package/src/components/chat/entity-cards/dispatch.tsx +43 -23
- package/src/components/chat/hooks/.use-unified-chat.md +5 -1
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +4 -7
- package/src/components/chat/hooks/use-unified-chat.ts +8 -2
- package/src/components/chat/nav-link-anchor-via-runtime.tsx +8 -10
- package/src/components/chat/types/.unified-chat-state.types.md +2 -1
- package/src/components/chat/types/unified-chat-state.types.ts +9 -2
- package/src/components/chat/utils/__tests__/execute-navigation.test.ts +190 -0
- package/src/components/chat/utils/__tests__/resolve-fetched-card-href.test.ts +75 -2
- package/src/components/chat/utils/chat-nav-resolution.ts +9 -3
- package/src/components/chat/utils/execute-navigation.ts +32 -2
- package/src/components/chat/utils/index.ts +1 -1
- package/src/components/chat/utils/nav-anchor-props.ts +4 -2
- package/src/components/chat/utils/resolve-fetched-card-href.ts +50 -2
- package/src/components/features/board/.board.md +1 -0
- package/src/components/features/board/board.tsx +11 -1
- package/src/components/features/sortable-list/.sortable-list.md +23 -12
- package/src/components/features/sortable-list/index.ts +2 -1
- package/src/components/features/sortable-list/sortable-list.tsx +16 -5
- package/src/components/features/sortable-list/sortable-move-buttons.tsx +81 -0
- package/src/components/features/sortable-list/use-sortable-item.ts +25 -13
- package/src/components/features/ticket-status-config-list/ticket-status-config-list.tsx +55 -62
- package/src/components/ui/.ticket-status-config-row.md +11 -9
- package/src/components/ui/input.tsx +5 -2
- package/src/components/ui/textarea.tsx +5 -2
- package/src/components/ui/ticket-status-config-row.tsx +162 -70
- package/src/hooks/ui/.index.md +1 -0
- package/src/hooks/ui/.use-drag-and-drop-enabled.md +32 -0
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/use-drag-and-drop-enabled.ts +37 -0
- package/src/hooks/ui/use-media-query.ts +3 -2
- package/src/utils/.content-href.md +1 -1
- package/src/utils/__tests__/content-href.test.ts +3 -0
- package/src/utils/content-href.ts +30 -3
- package/src/utils/index.ts +1 -0
- package/dist/chunk-2BFZHTSD.cjs.map +0 -1
- package/dist/chunk-36NFJULV.cjs.map +0 -1
- package/dist/chunk-4D2BR756.js.map +0 -1
- package/dist/chunk-75X6BRID.cjs.map +0 -1
- package/dist/chunk-HM2R5MDR.js.map +0 -1
- package/dist/chunk-JQI726T3.js.map +0 -1
- package/dist/chunk-KE2TCUOH.cjs.map +0 -1
- package/dist/chunk-MIY5FYX3.cjs.map +0 -1
- package/dist/chunk-NP3XN6ZX.js.map +0 -1
- package/dist/chunk-PQETCTJ4.js.map +0 -1
- package/dist/chunk-QYQXAXVR.cjs.map +0 -1
- package/dist/chunk-W6JA6SFD.js.map +0 -1
- package/dist/components/chat/utils/nav-click-handler.d.ts +0 -24
- package/dist/components/chat/utils/nav-click-handler.d.ts.map +0 -1
- package/src/components/chat/utils/.nav-click-handler.md +0 -43
- package/src/components/chat/utils/nav-click-handler.ts +0 -34
- /package/dist/{chunk-IQFXDS5O.js.map → chunk-4WSKSNXL.js.map} +0 -0
- /package/dist/{chunk-NEKT67O5.js.map → chunk-5Y6TPFW7.js.map} +0 -0
- /package/dist/{chunk-AQ5MZVYN.js.map → chunk-AKPLEWMH.js.map} +0 -0
- /package/dist/{chunk-RUJKOU5F.js.map → chunk-BW4WDUSI.js.map} +0 -0
- /package/dist/{chunk-FCVAVOXS.js.map → chunk-D44S2QLI.js.map} +0 -0
- /package/dist/{chunk-U3SDSA5K.js.map → chunk-DB3TV4CW.js.map} +0 -0
- /package/dist/{chunk-ZBJ6XSDO.js.map → chunk-H4BPHGU5.js.map} +0 -0
- /package/dist/{chunk-4KR7JWKB.js.map → chunk-HGA74X35.js.map} +0 -0
- /package/dist/{chunk-2YCUEUIZ.js.map → chunk-L2CF2QZW.js.map} +0 -0
- /package/dist/{chunk-JCH6HGQD.js.map → chunk-MCWAZ5XI.js.map} +0 -0
- /package/dist/{chunk-LYOGSVFO.js.map → chunk-O2FTVZOD.js.map} +0 -0
- /package/dist/{chunk-THDGDYJ3.js.map → chunk-PMENIZXI.js.map} +0 -0
- /package/dist/{chunk-TU32TSAK.js.map → chunk-PWOYEUIQ.js.map} +0 -0
- /package/dist/{chunk-RSMUAAPW.js.map → chunk-QCP2KV7P.js.map} +0 -0
- /package/dist/{chunk-RNUH5IHZ.js.map → chunk-QGJYWLF4.js.map} +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
<!-- source-hash:
|
|
2
|
-
A client-side React component that renders a single row in a ticket status configuration UI, supporting both system-defined and custom ticket statuses with
|
|
1
|
+
<!-- source-hash: manual -->
|
|
2
|
+
A client-side React component that renders a single row in a ticket status configuration UI, supporting both system-defined and custom ticket statuses with reordering, color picking, and delete controls.
|
|
3
3
|
|
|
4
4
|
## Key Components
|
|
5
5
|
|
|
6
6
|
### `TicketStatusConfigRow`
|
|
7
7
|
The primary exported component. Renders a row containing:
|
|
8
|
-
- **Drag handle** — interactive for custom rows, decorative/disabled for system rows
|
|
9
|
-
- **Name input** — editable for custom, read-only for system rows
|
|
10
|
-
- **Color controls** — `ColorPresetSelect` and `ColorPickerInput` shown only for custom rows with color support
|
|
11
|
-
- **Live preview** — `TicketStatusTag` reflecting current name and color
|
|
8
|
+
- **Drag handle** — interactive for custom rows, decorative/disabled for system rows. The whole rail is rendered only while `useDragAndDropEnabled()` holds; on touch/narrow viewports it disappears and the `moveButtons` node (an up/down `SortableMoveButtons` pair handed in by `TicketStatusConfigList`) renders beside the delete button instead
|
|
9
|
+
- **Name input** — editable for custom, read-only for system rows; carries a visible "Status Name" label (the rail, chip and controls offset below the label line to align with the field)
|
|
10
|
+
- **Color controls** — `ColorPresetSelect` (labelled "Color") and `ColorPickerInput` shown only for custom rows with color support
|
|
11
|
+
- **Live preview** — `TicketStatusTag` reflecting current name and color. Inline right on desktop (lg+); below the fields on tablet (own row, right-aligned) and mobile (beside the color select). System rows keep it beside the name everywhere
|
|
12
|
+
- **Transparent background** — only the border outlines the row; the dragged row gets an opaque `bg-ods-bg` so it can travel over siblings
|
|
12
13
|
- **Action button** — info tooltip icon for system rows; delete button (with optional disabled state tooltip) for custom rows
|
|
13
14
|
|
|
14
15
|
### `WithLeftTooltip` (internal)
|
|
@@ -22,7 +23,8 @@ The full props interface, key fields:
|
|
|
22
23
|
| `variant` | `'system'` or `'custom'` — controls editability and available actions |
|
|
23
24
|
| `statusKey` | Canonical key forwarded to `TicketStatusTag` |
|
|
24
25
|
| `onColorChange` | Callback receiving `{ color, preset? }` |
|
|
25
|
-
| `dragHandleProps`
|
|
26
|
+
| `dragHandleProps` | `SortableDragHandleProps` from `useSortableItem` (spread whole onto the grip) |
|
|
27
|
+
| `moveButtons` | Touch fallback node (`SortableRowRenderArgs.moveButtons`), rendered beside delete |
|
|
26
28
|
| `deleteDisabledReason` | Shown as tooltip when delete is disabled |
|
|
27
29
|
| `isDragging` | Applies visual feedback during drag |
|
|
28
30
|
|
|
@@ -46,9 +48,9 @@ import { TicketStatusConfigRow } from './ticket-status-config-row'
|
|
|
46
48
|
onDelete={handleDelete}
|
|
47
49
|
deleteDisabled={isOnlyStatus}
|
|
48
50
|
deleteDisabledReason="At least one status is required"
|
|
49
|
-
dragHandleProps={
|
|
50
|
-
dragHandleAttributes={attributes}
|
|
51
|
+
dragHandleProps={dragHandleProps}
|
|
51
52
|
isDragging={isDragging}
|
|
53
|
+
moveButtons={moveButtons}
|
|
52
54
|
/>
|
|
53
55
|
|
|
54
56
|
// System row (read-only, no delete)
|
|
@@ -14,6 +14,8 @@ export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
|
14
14
|
endAdornment?: ReactNode;
|
|
15
15
|
/** Label text displayed above the input */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** Label scale forwarded to FieldWrapper ('large' = text-h4 for designs with body-scale field titles) */
|
|
18
|
+
labelVariant?: 'default' | 'large';
|
|
17
19
|
/** Status message displayed below the input */
|
|
18
20
|
error?: string;
|
|
19
21
|
/** Color variant for the message: "error" (red), "warning" (yellow), "success" (green) or "muted" (grey).
|
|
@@ -37,6 +39,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
37
39
|
startAdornment,
|
|
38
40
|
endAdornment,
|
|
39
41
|
label,
|
|
42
|
+
labelVariant,
|
|
40
43
|
error,
|
|
41
44
|
errorVariant = 'error',
|
|
42
45
|
loading = false,
|
|
@@ -75,7 +78,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
75
78
|
/>
|
|
76
79
|
);
|
|
77
80
|
return label ? (
|
|
78
|
-
<FieldWrapper label={label} error={error} errorVariant={errorVariant}>
|
|
81
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error} errorVariant={errorVariant}>
|
|
79
82
|
{rangeInput}
|
|
80
83
|
</FieldWrapper>
|
|
81
84
|
) : (
|
|
@@ -149,7 +152,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
149
152
|
);
|
|
150
153
|
|
|
151
154
|
return (
|
|
152
|
-
<FieldWrapper label={label} error={error} errorVariant={errorVariant}>
|
|
155
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error} errorVariant={errorVariant}>
|
|
153
156
|
{content}
|
|
154
157
|
</FieldWrapper>
|
|
155
158
|
);
|
|
@@ -10,6 +10,8 @@ export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElemen
|
|
|
10
10
|
invalid?: boolean;
|
|
11
11
|
/** Label text displayed above the textarea */
|
|
12
12
|
label?: string;
|
|
13
|
+
/** Label scale forwarded to FieldWrapper ('large' = text-h4 for designs with body-scale field titles) */
|
|
14
|
+
labelVariant?: 'default' | 'large';
|
|
13
15
|
/** Error message displayed below the textarea */
|
|
14
16
|
error?: string;
|
|
15
17
|
/** Element rendered at the right edge of the field (e.g. send icon). */
|
|
@@ -41,6 +43,7 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
41
43
|
className,
|
|
42
44
|
invalid = false,
|
|
43
45
|
label,
|
|
46
|
+
labelVariant,
|
|
44
47
|
error,
|
|
45
48
|
endIcon,
|
|
46
49
|
endIconAsButton = false,
|
|
@@ -66,7 +69,7 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
66
69
|
// border/bg/hover all live on the textarea itself.
|
|
67
70
|
if (!hasAdornment) {
|
|
68
71
|
return (
|
|
69
|
-
<FieldWrapper label={label} error={error}>
|
|
72
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error}>
|
|
70
73
|
<textarea
|
|
71
74
|
className={cn(
|
|
72
75
|
'flex min-h-[96px] w-full rounded-[6px] border p-3',
|
|
@@ -214,7 +217,7 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
214
217
|
);
|
|
215
218
|
|
|
216
219
|
return (
|
|
217
|
-
<FieldWrapper label={label} error={error}>
|
|
220
|
+
<FieldWrapper label={label} labelVariant={labelVariant} error={error}>
|
|
218
221
|
{content}
|
|
219
222
|
</FieldWrapper>
|
|
220
223
|
);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { type ReactNode, useId } from 'react';
|
|
4
|
+
import { useDragAndDropEnabled } from '../../hooks/ui/use-drag-and-drop-enabled';
|
|
4
5
|
import { cn } from '../../utils/cn';
|
|
6
|
+
import type { SortableDragHandleProps } from '../features/sortable-list/use-sortable-item';
|
|
5
7
|
import { DraggerIcon } from '../icons-v2-generated/interface/dragger-icon';
|
|
6
8
|
import { TrashIcon } from '../icons-v2-generated/interface/trash-icon';
|
|
7
9
|
import { InfoCircleIcon } from '../icons-v2-generated/signs-and-symbols/info-circle-icon';
|
|
8
10
|
import { Button } from './button';
|
|
9
11
|
import { ColorPresetSelect, ColorPickerInput } from './color-preset-select';
|
|
10
12
|
import { Input } from './input';
|
|
13
|
+
import { Label } from './label';
|
|
11
14
|
import type { TagProps } from './tag';
|
|
12
15
|
import { TicketStatusTag } from './ticket-status-tag';
|
|
13
16
|
import { TouchFriendlyTooltip } from './touch-friendly-tooltip';
|
|
@@ -31,9 +34,14 @@ export interface TicketStatusConfigRowProps {
|
|
|
31
34
|
onDelete?: () => void;
|
|
32
35
|
deleteDisabled?: boolean;
|
|
33
36
|
deleteDisabledReason?: string;
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
/** From `useSortableItem` (via `TicketStatusConfigList`'s render args). */
|
|
38
|
+
dragHandleProps?: SortableDragHandleProps;
|
|
36
39
|
isDragging?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The touch replacement for the drag handle (`SortableRowRenderArgs.moveButtons`).
|
|
42
|
+
* Rendered beside the delete button; the drag rail is hidden on touch anyway.
|
|
43
|
+
*/
|
|
44
|
+
moveButtons?: ReactNode;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
export function TicketStatusConfigRow({
|
|
@@ -50,9 +58,13 @@ export function TicketStatusConfigRow({
|
|
|
50
58
|
deleteDisabled,
|
|
51
59
|
deleteDisabledReason,
|
|
52
60
|
dragHandleProps,
|
|
53
|
-
dragHandleAttributes,
|
|
54
61
|
isDragging,
|
|
62
|
+
moveButtons,
|
|
55
63
|
}: TicketStatusConfigRowProps) {
|
|
64
|
+
// Standalone system rows render outside any SortableList, so the row answers
|
|
65
|
+
// "is there a drag rail at all?" itself — same media gate the list uses.
|
|
66
|
+
const dragAndDropEnabled = useDragAndDropEnabled();
|
|
67
|
+
const nameInputId = useId();
|
|
56
68
|
const isSystem = variant === 'system';
|
|
57
69
|
const isCustomColor = !isSystem && presetKey === undefined;
|
|
58
70
|
const previewColor = isSystem ? undefined : color;
|
|
@@ -60,101 +72,181 @@ export function TicketStatusConfigRow({
|
|
|
60
72
|
// Custom rows render as filled pills (no border); system rows take the
|
|
61
73
|
// page-configured variant (outline/primary). text-h5 already uppercases.
|
|
62
74
|
const previewVariant: SystemTagVariant | undefined = systemTagVariant ?? (isSystem ? undefined : 'primary');
|
|
63
|
-
|
|
75
|
+
|
|
76
|
+
// The fields carry a visible label, so everything that sits beside the
|
|
77
|
+
// 44/48px field row (rail, chip, controls) drops below the label line by
|
|
78
|
+
// exactly its height: the h4 line + the label's mb-1.
|
|
79
|
+
const fieldRowOffset = 'mt-[calc(var(--font-line-space-h4-body)+0.25rem)]';
|
|
80
|
+
|
|
81
|
+
const chip = (
|
|
82
|
+
<TicketStatusTag
|
|
83
|
+
status={statusKey}
|
|
84
|
+
label={name || ' '}
|
|
85
|
+
color={previewColor}
|
|
86
|
+
variant={previewVariant}
|
|
87
|
+
showIcon={isSystem}
|
|
88
|
+
className="max-w-full"
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
64
91
|
|
|
65
92
|
return (
|
|
66
93
|
<div
|
|
67
94
|
className={cn(
|
|
68
|
-
|
|
95
|
+
// Transparent background — only the border outlines the row.
|
|
96
|
+
'flex w-full items-start gap-3 rounded-md border border-ods-border md:gap-[var(--spacing-system-m)]',
|
|
69
97
|
'p-[var(--spacing-system-m)]',
|
|
70
|
-
|
|
98
|
+
// The dragged row travels over its siblings, so it needs an opaque back.
|
|
99
|
+
isDragging && 'bg-ods-bg opacity-70 shadow-lg',
|
|
71
100
|
)}
|
|
72
101
|
>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
{dragAndDropEnabled && (
|
|
103
|
+
<div
|
|
104
|
+
className={cn(
|
|
105
|
+
'flex h-11 w-8 shrink-0 items-center justify-center text-ods-text-secondary md:h-12',
|
|
106
|
+
fieldRowOffset,
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{isSystem ? (
|
|
110
|
+
<DraggerIcon size={24} aria-hidden className="opacity-40" />
|
|
111
|
+
) : (
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
aria-label="Drag to reorder"
|
|
115
|
+
className="flex size-6 cursor-grab items-center justify-center rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ods-focus active:cursor-grabbing"
|
|
116
|
+
{...dragHandleProps}
|
|
117
|
+
>
|
|
118
|
+
<DraggerIcon size={24} />
|
|
119
|
+
</button>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
88
123
|
|
|
89
|
-
|
|
90
|
-
|
|
124
|
+
{/* Every row spans the same equal column slots — two on tablet
|
|
125
|
+
(name + color for custom, name + chip for system), four on desktop
|
|
126
|
+
(name, color, a reserved slot, chip zone) with invisible spacers
|
|
127
|
+
standing in for the cells a row doesn't use — so the name field is one
|
|
128
|
+
width on every row and the columns align across the whole list.
|
|
129
|
+
The trailing controls are a WRAP ITEM (ordered per breakpoint), not a
|
|
130
|
+
full-height sibling column, so the lower rows span the full card width
|
|
131
|
+
and the chip pins to the card's right edge on tablet/mobile. */}
|
|
132
|
+
<div
|
|
133
|
+
className={cn(
|
|
134
|
+
'min-w-0 flex-1 items-start gap-x-3 gap-y-[var(--spacing-system-m)] md:gap-x-[var(--spacing-system-m)]',
|
|
135
|
+
isSystem
|
|
136
|
+
? 'flex flex-wrap'
|
|
137
|
+
: // Mobile custom rows are a strict two-column grid, per the mock:
|
|
138
|
+
// fields (name / color / hex, all one width) on the left, the
|
|
139
|
+
// controls and the chip stacked in the right column. From md up
|
|
140
|
+
// the same children flow as a wrapping flex row.
|
|
141
|
+
'grid grid-cols-[minmax(0,1fr)_auto] md:flex md:flex-wrap',
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
{/* System rows keep the chip beside the name on every width, so the name
|
|
145
|
+
shares its row. */}
|
|
146
|
+
<div className="order-1 flex min-w-0 grow basis-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
147
|
+
<Label variant="large" htmlFor={nameInputId}>
|
|
148
|
+
Status Name
|
|
149
|
+
</Label>
|
|
91
150
|
<Input
|
|
151
|
+
id={nameInputId}
|
|
92
152
|
value={name}
|
|
93
153
|
onChange={e => onNameChange?.(e.target.value)}
|
|
94
154
|
disabled={isSystem}
|
|
95
155
|
readOnly={isSystem}
|
|
96
|
-
aria-label="Status name"
|
|
97
156
|
maxLength={50}
|
|
98
157
|
/>
|
|
99
158
|
</div>
|
|
100
159
|
|
|
101
160
|
{showColorPicker ? (
|
|
102
|
-
<div className=
|
|
161
|
+
<div className="order-3 flex min-w-0 grow basis-full flex-col gap-[var(--spacing-system-xxs)] md:order-2 md:basis-0">
|
|
162
|
+
<Label variant="large">Color</Label>
|
|
103
163
|
<ColorPresetSelect value={color} presetKey={presetKey} onChange={onColorChange} />
|
|
164
|
+
{/* Custom hex: a second field row inside the Color column, directly
|
|
165
|
+
under the select (per design). */}
|
|
166
|
+
{isCustomColor && (
|
|
167
|
+
<div className="mt-[var(--spacing-system-xxs)] flex min-w-0">
|
|
168
|
+
<ColorPickerInput value={color} onChange={next => onColorChange({ color: next, preset: undefined })} />
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
104
171
|
</div>
|
|
105
172
|
) : (
|
|
106
|
-
<div aria-hidden className=
|
|
173
|
+
<div aria-hidden className="hidden min-w-0 grow basis-0 lg:order-2 lg:block" />
|
|
107
174
|
)}
|
|
108
175
|
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
176
|
+
{/* Reserved desktop column slot (the mock's "No activity Indicator" zone). */}
|
|
177
|
+
<div aria-hidden className="hidden min-w-0 grow basis-0 lg:order-3 lg:block" />
|
|
178
|
+
|
|
179
|
+
{isSystem ? (
|
|
180
|
+
<div
|
|
181
|
+
className={cn(
|
|
182
|
+
'order-2 flex h-11 min-w-0 grow basis-0 items-center justify-end md:h-12 lg:order-4',
|
|
183
|
+
fieldRowOffset,
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{chip}
|
|
112
187
|
</div>
|
|
113
188
|
) : (
|
|
114
|
-
|
|
189
|
+
<>
|
|
190
|
+
{/* Desktop (lg+): the chip rides the first row, in its own column
|
|
191
|
+
before the controls. */}
|
|
192
|
+
<div
|
|
193
|
+
className={cn(
|
|
194
|
+
'hidden h-12 min-w-0 grow basis-0 items-center justify-end lg:order-4 lg:flex',
|
|
195
|
+
fieldRowOffset,
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{chip}
|
|
199
|
+
</div>
|
|
200
|
+
{/* Tablet: the chip takes a full row of its own, pinned to the
|
|
201
|
+
card's right edge (the controls above no longer narrow it). */}
|
|
202
|
+
<div className="hidden min-w-0 basis-full items-center justify-end md:order-4 md:flex lg:hidden">
|
|
203
|
+
{chip}
|
|
204
|
+
</div>
|
|
205
|
+
{/* Mobile: the chip fills the second grid column beside the color
|
|
206
|
+
field, left-aligned with the controls above it. */}
|
|
207
|
+
<div className={cn('order-4 flex h-11 min-w-0 items-center md:hidden', fieldRowOffset)}>{chip}</div>
|
|
208
|
+
</>
|
|
115
209
|
)}
|
|
116
210
|
|
|
117
|
-
<div
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
<div
|
|
212
|
+
className={cn(
|
|
213
|
+
'flex h-11 shrink-0 items-center justify-end gap-[var(--spacing-system-s)] md:h-12 lg:order-5',
|
|
214
|
+
isSystem ? 'order-3' : 'order-2 md:order-3',
|
|
215
|
+
fieldRowOffset,
|
|
216
|
+
)}
|
|
217
|
+
>
|
|
218
|
+
{!isSystem && moveButtons}
|
|
219
|
+
{isSystem ? (
|
|
220
|
+
<div className="flex w-11 justify-center md:w-12">
|
|
221
|
+
<TouchFriendlyTooltip content={systemTooltip}>
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
aria-label={systemTooltip ?? 'System status'}
|
|
225
|
+
className="flex size-6 items-center justify-center text-ods-text-secondary outline-none focus-visible:ring-2 focus-visible:ring-ods-focus"
|
|
226
|
+
>
|
|
227
|
+
<InfoCircleIcon size={24} />
|
|
228
|
+
</button>
|
|
229
|
+
</TouchFriendlyTooltip>
|
|
230
|
+
</div>
|
|
231
|
+
) : (
|
|
232
|
+
<TouchFriendlyTooltip content={deleteDisabled ? deleteDisabledReason : undefined}>
|
|
233
|
+
<span className="inline-flex">
|
|
234
|
+
<Button
|
|
235
|
+
type="button"
|
|
236
|
+
variant="outline"
|
|
237
|
+
size="icon"
|
|
238
|
+
aria-label="Delete status"
|
|
239
|
+
disabled={deleteDisabled}
|
|
240
|
+
onClick={deleteDisabled ? undefined : onDelete}
|
|
241
|
+
className={cn('size-11 md:size-12', deleteDisabled && 'pointer-events-none')}
|
|
242
|
+
>
|
|
243
|
+
<TrashIcon className="text-ods-error" />
|
|
244
|
+
</Button>
|
|
245
|
+
</span>
|
|
246
|
+
</TouchFriendlyTooltip>
|
|
247
|
+
)}
|
|
126
248
|
</div>
|
|
127
249
|
</div>
|
|
128
|
-
|
|
129
|
-
<div className="flex h-11 w-12 shrink-0 items-center justify-center md:h-12">
|
|
130
|
-
{isSystem ? (
|
|
131
|
-
<TouchFriendlyTooltip content={systemTooltip}>
|
|
132
|
-
<button
|
|
133
|
-
type="button"
|
|
134
|
-
aria-label={systemTooltip ?? 'System status'}
|
|
135
|
-
className="flex size-6 items-center justify-center text-ods-text-secondary outline-none focus-visible:ring-2 focus-visible:ring-ods-focus"
|
|
136
|
-
>
|
|
137
|
-
<InfoCircleIcon size={24} />
|
|
138
|
-
</button>
|
|
139
|
-
</TouchFriendlyTooltip>
|
|
140
|
-
) : (
|
|
141
|
-
<TouchFriendlyTooltip content={deleteDisabled ? deleteDisabledReason : undefined}>
|
|
142
|
-
<span className="inline-flex">
|
|
143
|
-
<Button
|
|
144
|
-
type="button"
|
|
145
|
-
variant="outline"
|
|
146
|
-
size="icon"
|
|
147
|
-
aria-label="Delete status"
|
|
148
|
-
disabled={deleteDisabled}
|
|
149
|
-
onClick={deleteDisabled ? undefined : onDelete}
|
|
150
|
-
className={cn('size-11 md:size-12', deleteDisabled && 'pointer-events-none')}
|
|
151
|
-
>
|
|
152
|
-
<TrashIcon className="text-ods-error" />
|
|
153
|
-
</Button>
|
|
154
|
-
</span>
|
|
155
|
-
</TouchFriendlyTooltip>
|
|
156
|
-
)}
|
|
157
|
-
</div>
|
|
158
250
|
</div>
|
|
159
251
|
);
|
|
160
252
|
}
|
package/src/hooks/ui/.index.md
CHANGED
|
@@ -8,6 +8,7 @@ Barrel export file for the UI hooks library, re-exporting all custom React hooks
|
|
|
8
8
|
| `useAutoLimitTags` | Limits visible tags based on container width |
|
|
9
9
|
| `useDebounce` | Delays value updates by a specified duration |
|
|
10
10
|
| `useDeferredError` | Defers error state for async workflows |
|
|
11
|
+
| `useDragAndDropEnabled` | Whether DnD is initialized at all — fine pointer + hover + 800px, else touch fallbacks |
|
|
11
12
|
| `useFocusTrap` | Traps keyboard focus within a DOM subtree |
|
|
12
13
|
| `useHeaderHeight` | Tracks the application header's pixel height |
|
|
13
14
|
| `useHorizontalScrollbar` | Detects and manages horizontal scrollbar presence |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!-- source-hash: manual -->
|
|
2
|
+
The one rule for whether drag-and-drop is initialized anywhere in the UI. `SortableList` and
|
|
3
|
+
`Board` consume it themselves, so hosts get correct touch behavior without wiring anything.
|
|
4
|
+
|
|
5
|
+
## Key Components
|
|
6
|
+
|
|
7
|
+
| Export | Type | Description |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `useDragAndDropEnabled` | Hook | `true` only when `(pointer: fine) and (hover: hover) and (min-width: 800px)` matches |
|
|
10
|
+
| `DRAG_AND_DROP_MEDIA_QUERY` | Const | The query itself, composed from `breakpoints.md` — no new breakpoint |
|
|
11
|
+
|
|
12
|
+
## Why pointer type AND breakpoint
|
|
13
|
+
|
|
14
|
+
- A breakpoint alone fails on tablets: an iPad in landscape is 1024-1366px wide and would pass any
|
|
15
|
+
sane `min-width` — exactly the case being excluded, because the HTML5 drag-and-drop API Pragmatic
|
|
16
|
+
builds on does not fire under touch.
|
|
17
|
+
- `pointer: coarse` alone fails the other way: a touchscreen laptop with a trackpad reports
|
|
18
|
+
`pointer: fine` and should keep DnD, but in a narrow window its lists collapse to the mobile
|
|
19
|
+
layout where a drag handle is out of place.
|
|
20
|
+
- No `'ontouchstart' in window`, no UA sniffing: the first is true on touch laptops, the second is
|
|
21
|
+
neither accurate nor reactive. A media query re-evaluates live when a mouse is attached or the
|
|
22
|
+
window resizes.
|
|
23
|
+
|
|
24
|
+
## SSR
|
|
25
|
+
|
|
26
|
+
Returns `false` during SSR and the first client render (`useMediaQuery` is still `undefined`):
|
|
27
|
+
DnD off means no dead drag handle in server HTML, and the layout effect settles the real answer
|
|
28
|
+
before first paint, so a desktop user never sees the touch fallback flash.
|
|
29
|
+
|
|
30
|
+
## Source
|
|
31
|
+
|
|
32
|
+
[`use-drag-and-drop-enabled.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/use-drag-and-drop-enabled.ts)
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export * from './use-auto-limit-tags';
|
|
3
3
|
export * from './use-debounce';
|
|
4
4
|
export * from './use-deferred-error';
|
|
5
|
+
export * from './use-drag-and-drop-enabled';
|
|
5
6
|
export * from './use-focus-trap';
|
|
6
7
|
export * from './use-header-height';
|
|
7
8
|
export * from './use-horizontal-scrollbar';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { breakpoints, useMediaQuery } from './use-media-query';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The one rule for whether drag-and-drop is initialized at all.
|
|
7
|
+
*
|
|
8
|
+
* Pointer type AND breakpoint, never either alone:
|
|
9
|
+
* - A breakpoint alone fails on tablets: an iPad in landscape is 1024-1366px
|
|
10
|
+
* wide and would pass any sane `min-width` — exactly the case being excluded,
|
|
11
|
+
* because the HTML5 drag-and-drop API Pragmatic builds on does not fire under
|
|
12
|
+
* touch.
|
|
13
|
+
* - `pointer: coarse` alone fails the other way: a touchscreen laptop with a
|
|
14
|
+
* trackpad reports `pointer: fine` and should keep DnD, but in a narrow
|
|
15
|
+
* window its lists collapse to the mobile layout where a drag handle is out
|
|
16
|
+
* of place.
|
|
17
|
+
* - No `'ontouchstart' in window`, no UA sniffing: the first is true on touch
|
|
18
|
+
* laptops, the second is neither accurate nor reactive. A media query
|
|
19
|
+
* re-evaluates live when a mouse is attached or the window resizes.
|
|
20
|
+
*
|
|
21
|
+
* `breakpoints.md` (800px) is the existing tablet step — no new breakpoint.
|
|
22
|
+
*/
|
|
23
|
+
export const DRAG_AND_DROP_MEDIA_QUERY = `(pointer: fine) and (hover: hover) and ${breakpoints.md}`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether drag-and-drop should be available on this device/viewport.
|
|
27
|
+
*
|
|
28
|
+
* `false` on touch/narrow screens — there the reordering surfaces render
|
|
29
|
+
* explicit controls (`SortableMoveButtons`) instead of a drag handle, and no
|
|
30
|
+
* DnD is initialized at all. Also `false` during SSR and the first client
|
|
31
|
+
* render (`useMediaQuery` is still `undefined`): defaulting to "off" renders
|
|
32
|
+
* no dead drag handle, and the underlying layout effect settles the real
|
|
33
|
+
* answer before first paint, so a desktop user never sees the fallback flash.
|
|
34
|
+
*/
|
|
35
|
+
export function useDragAndDropEnabled(): boolean {
|
|
36
|
+
return useMediaQuery(DRAG_AND_DROP_MEDIA_QUERY) ?? false;
|
|
37
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Hook to check if a media query matches
|
|
@@ -10,7 +11,7 @@ import { useLayoutEffect, useState } from 'react';
|
|
|
10
11
|
export function useMediaQuery(query: string): boolean | undefined {
|
|
11
12
|
const [matches, setMatches] = useState<boolean | undefined>(undefined);
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
useIsomorphicLayoutEffect(() => {
|
|
14
15
|
const matchMedia = window.matchMedia(query);
|
|
15
16
|
|
|
16
17
|
const handleChange = () => {
|
|
@@ -15,7 +15,7 @@ Unified content-href authority for all embeddable surfaces — resolves content
|
|
|
15
15
|
|
|
16
16
|
### Functions
|
|
17
17
|
|
|
18
|
-
- **`makeComposeContentUrl(opts)`** — Factory that builds the embedder's `composeContentUrl`. Resolution order: explicit `overrides` → hosted in-app route → verbatim `externalUrl` → hub-origin fallback.
|
|
18
|
+
- **`makeComposeContentUrl(opts)`** — Factory that builds the embedder's `composeContentUrl`. Resolution order: explicit `overrides` → hosted in-app route → verbatim `externalUrl` → hub-origin fallback. Only the `overrides` branch returns `hostOverride: true`; fetch-mode chat cards rank that above the fetched row's own url (`pickFetchedCardHref`), and everything else below it.
|
|
19
19
|
- **`buildDefaultHref(basePath, slug)`** — Fallback href when no `composeContentUrl` is wired (single-platform embedders).
|
|
20
20
|
- **`resolveContentHref(composeContentUrl, args)`** — Ternary helper used by catalog/detail views: delegates to `composeContentUrl` when wired, otherwise calls `buildDefaultHref`.
|
|
21
21
|
|
|
@@ -188,6 +188,9 @@ describe('makeComposeContentUrl (unified single-object seam)', () => {
|
|
|
188
188
|
expect(c({ type: 'product_release', identifier: 'v2', externalUrl: `${HUB}/releases/v2` })).toEqual({
|
|
189
189
|
href: '/custom/v2',
|
|
190
190
|
targetPlatform: 'openframe',
|
|
191
|
+
// Only this branch is flagged — fetch-mode cards rank it above the
|
|
192
|
+
// fetched row's own url (`pickFetchedCardHref`).
|
|
193
|
+
hostOverride: true,
|
|
191
194
|
});
|
|
192
195
|
});
|
|
193
196
|
|
|
@@ -112,10 +112,34 @@ export interface ContentHrefOptions {
|
|
|
112
112
|
overrides?: Record<string, (identifier: string) => { href: string; targetPlatform: string | null }>;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/** What the seam resolves a content reference to. */
|
|
116
|
+
export interface ComposedContentUrl {
|
|
117
|
+
href: string;
|
|
118
|
+
targetPlatform: string | null;
|
|
119
|
+
/**
|
|
120
|
+
* Set ONLY when the href came from the host's EXPLICIT per-type `overrides`
|
|
121
|
+
* entry — i.e. the host stated where this type lives in ITS app, rather than
|
|
122
|
+
* the composer falling through to a hosted-suffix or `<origin>/<type>/<id>`
|
|
123
|
+
* synthesis.
|
|
124
|
+
*
|
|
125
|
+
* The distinction is load-bearing for fetch-mode chat cards whose fetched row
|
|
126
|
+
* already carries a destination (`refHydratedEntry`'s `item.url`, minted by
|
|
127
|
+
* the CONTENT HOST for its OWN surface). That url must beat a synthesized
|
|
128
|
+
* guess, but must NOT beat a host that has explicitly re-homed the type:
|
|
129
|
+
* a HubSpot ticket's `item.url` is the hub's `/tickets?ticket=…`, which in an
|
|
130
|
+
* embedder that serves the same ticket at `/help-center/tickets` silently
|
|
131
|
+
* resolves against the embedder's own unrelated `/tickets` route.
|
|
132
|
+
*
|
|
133
|
+
* Optional and absent by default, so a hand-written composer (the hub's
|
|
134
|
+
* cross-platform one) keeps the pre-existing "row url wins" behavior.
|
|
135
|
+
*/
|
|
136
|
+
hostOverride?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
115
139
|
/** The unified `composeContentUrl` seam shape on `ChatRuntime`. ALWAYS returns
|
|
116
140
|
* a tuple (never null) — the seam type is non-nullable and callers read
|
|
117
141
|
* `.href` unconditionally. */
|
|
118
|
-
export type ComposeContentUrl = (input: ComposeContentUrlInput) =>
|
|
142
|
+
export type ComposeContentUrl = (input: ComposeContentUrlInput) => ComposedContentUrl;
|
|
119
143
|
|
|
120
144
|
/**
|
|
121
145
|
* Owning platform of a content row, from the hydrated junction the list APIs
|
|
@@ -162,7 +186,8 @@ function lastPathSegment(url: string): string | null {
|
|
|
162
186
|
* Build the embedder's `composeContentUrl` for the unified seam.
|
|
163
187
|
*
|
|
164
188
|
* Resolution order (the merged rule used by BOTH page views and chat cards):
|
|
165
|
-
* 1. `overrides[type]` — explicit per-type href
|
|
189
|
+
* 1. `overrides[type]` — explicit per-type href, returned with
|
|
190
|
+
* `hostOverride: true` (the only branch that sets it).
|
|
166
191
|
* 2. `hostedTypes.has(type)` → relative `/<suffix>/<slug>` (in-app, soft-nav).
|
|
167
192
|
* Chat rows carry the hub URL not the slug, so the slug is recovered from
|
|
168
193
|
* `externalUrl`; page views pass the slug as `identifier`.
|
|
@@ -184,7 +209,9 @@ export function makeComposeContentUrl(opts: ContentHrefOptions): ComposeContentU
|
|
|
184
209
|
const type = canonicalContentRefType(rawType);
|
|
185
210
|
const override =
|
|
186
211
|
opts.overrides && Object.prototype.hasOwnProperty.call(opts.overrides, type) ? opts.overrides[type] : undefined;
|
|
187
|
-
|
|
212
|
+
// Flagged as an explicit host decision — see `ComposedContentUrl.hostOverride`
|
|
213
|
+
// for why a fetch-mode card has to tell this apart from the branches below.
|
|
214
|
+
if (override) return { ...override(identifier), hostOverride: true };
|
|
188
215
|
|
|
189
216
|
const seg = (Object.prototype.hasOwnProperty.call(suffixes, type) ? suffixes[type] : undefined) ?? type;
|
|
190
217
|
|
package/src/utils/index.ts
CHANGED