@flamingo-stack/openframe-frontend-core 0.0.502 → 0.0.503-snapshot.20260731162644
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-O45BOJMS.js → chunk-32J4AXWO.js} +3 -3
- package/dist/{chunk-I64ZGPA6.cjs → chunk-5JEL52KB.cjs} +3 -3
- package/dist/{chunk-I64ZGPA6.cjs.map → chunk-5JEL52KB.cjs.map} +1 -1
- package/dist/{chunk-5W6IMPJW.js → chunk-AFHWEJXM.js} +6 -6
- package/dist/{chunk-KOZAEN36.cjs → chunk-AI7ASU6V.cjs} +93 -93
- package/dist/{chunk-KOZAEN36.cjs.map → chunk-AI7ASU6V.cjs.map} +1 -1
- package/dist/{chunk-C5R2OX7A.cjs → chunk-C6Q3BJDJ.cjs} +3 -3
- package/dist/chunk-C6Q3BJDJ.cjs.map +1 -0
- package/dist/{chunk-INZAM757.cjs → chunk-CQ6O6ER2.cjs} +12 -12
- package/dist/{chunk-INZAM757.cjs.map → chunk-CQ6O6ER2.cjs.map} +1 -1
- package/dist/{chunk-ZEEQTDDD.js → chunk-CSRGIQ2G.js} +3 -3
- package/dist/{chunk-2Z75A5MH.cjs → chunk-D3VTZBWC.cjs} +32 -32
- package/dist/{chunk-2Z75A5MH.cjs.map → chunk-D3VTZBWC.cjs.map} +1 -1
- package/dist/{chunk-ZGU3KBAM.js → chunk-EFBKOCEA.js} +3 -3
- package/dist/{chunk-EMMJIXDF.js → chunk-FW2U6U4P.js} +3336 -3292
- package/dist/chunk-FW2U6U4P.js.map +1 -0
- package/dist/{chunk-IRR6733A.js → chunk-GCVAZR52.js} +5 -5
- package/dist/{chunk-EC3G3BIT.js → chunk-HNDHCJV4.js} +6 -6
- package/dist/{chunk-IHYEMZ2B.cjs → chunk-IZTXVVGY.cjs} +40 -40
- package/dist/{chunk-IHYEMZ2B.cjs.map → chunk-IZTXVVGY.cjs.map} +1 -1
- package/dist/{chunk-NZEQGBVS.js → chunk-JCF55O7G.js} +2 -2
- package/dist/{chunk-JJV237MJ.js → chunk-JQDGP4CE.js} +4 -4
- package/dist/{chunk-4YDUZB5N.cjs → chunk-KHE3OBHD.cjs} +18 -18
- package/dist/{chunk-4YDUZB5N.cjs.map → chunk-KHE3OBHD.cjs.map} +1 -1
- package/dist/{chunk-AHZCNPTI.cjs → chunk-KZSQMZUL.cjs} +19 -19
- package/dist/{chunk-AHZCNPTI.cjs.map → chunk-KZSQMZUL.cjs.map} +1 -1
- package/dist/{chunk-LS3DMQTE.js → chunk-MRPFFHBK.js} +3 -3
- package/dist/{chunk-GE6DRWPN.cjs → chunk-NNQMA3N6.cjs} +12 -12
- package/dist/{chunk-GE6DRWPN.cjs.map → chunk-NNQMA3N6.cjs.map} +1 -1
- package/dist/{chunk-LA4UJ2D3.cjs → chunk-OK34RZWT.cjs} +73 -73
- package/dist/{chunk-LA4UJ2D3.cjs.map → chunk-OK34RZWT.cjs.map} +1 -1
- package/dist/{chunk-ABVQ54WR.cjs → chunk-Q6RMV5WK.cjs} +5 -5
- package/dist/{chunk-ABVQ54WR.cjs.map → chunk-Q6RMV5WK.cjs.map} +1 -1
- package/dist/{chunk-2Q6X4SE3.js → chunk-QFQBYNQJ.js} +4 -4
- package/dist/{chunk-WHJGRRK2.js → chunk-QW7SW5HY.js} +52 -43
- package/dist/chunk-QW7SW5HY.js.map +1 -0
- package/dist/{chunk-L5CI57RM.cjs → chunk-T3BF6JTQ.cjs} +52 -43
- package/dist/chunk-T3BF6JTQ.cjs.map +1 -0
- package/dist/{chunk-ADM7LRDY.cjs → chunk-US2F5GJO.cjs} +40 -40
- package/dist/{chunk-ADM7LRDY.cjs.map → chunk-US2F5GJO.cjs.map} +1 -1
- package/dist/{chunk-BRXSYV4M.cjs → chunk-WXARO2KA.cjs} +33 -33
- package/dist/{chunk-BRXSYV4M.cjs.map → chunk-WXARO2KA.cjs.map} +1 -1
- package/dist/{chunk-LBSC62XJ.cjs → chunk-X5USQ6UC.cjs} +995 -951
- package/dist/chunk-X5USQ6UC.cjs.map +1 -0
- package/dist/{chunk-VPQBQJ4S.js → chunk-XRGAQM3F.js} +2 -2
- package/dist/{chunk-QJMFF2YG.js → chunk-XT4P4JGJ.js} +6 -6
- package/dist/{chunk-P7IOCYGD.js → chunk-YKF25XRY.js} +3 -3
- package/dist/chunk-YKF25XRY.js.map +1 -0
- package/dist/components/case-studies/index.cjs +10 -10
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/content-loading-container.d.ts +1 -1
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- 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 +161 -161
- package/dist/components/index.js +12 -12
- package/dist/components/navigation/header-button.d.ts.map +1 -1
- package/dist/components/navigation/header.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
- package/dist/components/navigation/top-navigation.d.ts +5 -1
- package/dist/components/navigation/top-navigation.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/autocomplete.d.ts.map +1 -1
- package/dist/components/ui/button/button.d.ts +6 -5
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/floating-tooltip.d.ts +9 -4
- package/dist/components/ui/floating-tooltip.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/components/ui/service-card.d.ts.map +1 -1
- package/dist/components/ui/status-badge.d.ts +1 -1
- package/dist/components/ui/tag.d.ts +1 -1
- package/dist/components/ui/tag.d.ts.map +1 -1
- package/dist/components/ui/truncate-text.d.ts +14 -1
- package/dist/components/ui/truncate-text.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/ui/use-header-height.d.ts.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/components/features/time-tracker/time-tracker-header-button.tsx +1 -1
- package/src/components/navigation/app-header.tsx +10 -10
- package/src/components/navigation/header-button.tsx +4 -1
- package/src/components/navigation/header-mingo-button.tsx +2 -2
- package/src/components/navigation/header-skeleton.tsx +4 -4
- package/src/components/navigation/header.tsx +20 -14
- package/src/components/navigation/mingo-ai-button.tsx +23 -22
- package/src/components/navigation/mobile-burger-menu.tsx +3 -3
- package/src/components/navigation/top-navigation.tsx +17 -1
- package/src/components/ui/autocomplete.tsx +5 -2
- package/src/components/ui/button/button.tsx +165 -133
- package/src/components/ui/floating-tooltip.tsx +11 -5
- package/src/components/ui/service-card.tsx +18 -3
- package/src/components/ui/tag.tsx +34 -1
- package/src/components/ui/truncate-text.tsx +20 -0
- package/src/hooks/ui/use-header-height.ts +3 -1
- package/src/stories/TopNavigation.stories.tsx +20 -1
- package/dist/chunk-C5R2OX7A.cjs.map +0 -1
- package/dist/chunk-EMMJIXDF.js.map +0 -1
- package/dist/chunk-L5CI57RM.cjs.map +0 -1
- package/dist/chunk-LBSC62XJ.cjs.map +0 -1
- package/dist/chunk-P7IOCYGD.js.map +0 -1
- package/dist/chunk-WHJGRRK2.js.map +0 -1
- /package/dist/{chunk-O45BOJMS.js.map → chunk-32J4AXWO.js.map} +0 -0
- /package/dist/{chunk-5W6IMPJW.js.map → chunk-AFHWEJXM.js.map} +0 -0
- /package/dist/{chunk-ZEEQTDDD.js.map → chunk-CSRGIQ2G.js.map} +0 -0
- /package/dist/{chunk-ZGU3KBAM.js.map → chunk-EFBKOCEA.js.map} +0 -0
- /package/dist/{chunk-IRR6733A.js.map → chunk-GCVAZR52.js.map} +0 -0
- /package/dist/{chunk-EC3G3BIT.js.map → chunk-HNDHCJV4.js.map} +0 -0
- /package/dist/{chunk-NZEQGBVS.js.map → chunk-JCF55O7G.js.map} +0 -0
- /package/dist/{chunk-JJV237MJ.js.map → chunk-JQDGP4CE.js.map} +0 -0
- /package/dist/{chunk-LS3DMQTE.js.map → chunk-MRPFFHBK.js.map} +0 -0
- /package/dist/{chunk-2Q6X4SE3.js.map → chunk-QFQBYNQJ.js.map} +0 -0
- /package/dist/{chunk-VPQBQJ4S.js.map → chunk-XRGAQM3F.js.map} +0 -0
- /package/dist/{chunk-QJMFF2YG.js.map → chunk-XT4P4JGJ.js.map} +0 -0
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { Slot } from
|
|
4
|
-
import { cva, type VariantProps } from
|
|
5
|
-
import
|
|
6
|
-
import
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import Link from '../../../embed-shims/next-link';
|
|
7
7
|
|
|
8
|
-
import { cn } from
|
|
9
|
-
import {
|
|
8
|
+
import { cn } from '../../../utils/cn';
|
|
9
|
+
import {
|
|
10
|
+
buttonSurfaceClasses,
|
|
11
|
+
outlineBorderClasses,
|
|
12
|
+
splitDividerColorClasses,
|
|
13
|
+
splitGlyphSizeClasses,
|
|
14
|
+
} from './button-styles';
|
|
10
15
|
|
|
11
16
|
// Default layout: centered single content area, padding/gap on the button itself.
|
|
12
17
|
const buttonVariants = cva(
|
|
13
18
|
[
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
'relative inline-flex items-center justify-center gap-[var(--spacing-system-xsf)]',
|
|
20
|
+
'rounded-md whitespace-nowrap',
|
|
21
|
+
'transition-colors duration-200',
|
|
22
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus',
|
|
23
|
+
'disabled:pointer-events-none',
|
|
24
|
+
'[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:h-5 [&_svg]:w-5',
|
|
20
25
|
],
|
|
21
26
|
{
|
|
22
27
|
variants: {
|
|
@@ -30,9 +35,9 @@ const buttonVariants = cva(
|
|
|
30
35
|
overlay: buttonSurfaceClasses.overlay,
|
|
31
36
|
},
|
|
32
37
|
size: {
|
|
33
|
-
default:
|
|
34
|
-
small:
|
|
35
|
-
|
|
38
|
+
default: 'py-[var(--spacing-system-sf)] px-[var(--spacing-system-m)] text-h3 md:h-12 h-11',
|
|
39
|
+
small: 'p-[var(--spacing-system-xs)] text-h5 h-6 md:h-8',
|
|
40
|
+
'small-legacy': 'py-[var(--spacing-system-xs)] px-[var(--spacing-system-m)] h-10 text-[14px] font-bold', // Temporary alias for "small" — deprecated; grep size="small-legacy" (lib + hub) and migrate the remaining consumers before removal
|
|
36
41
|
// 24px pill for slim strips (announcement/promo bars, inline banner
|
|
37
42
|
// actions — Primer banner / Polaris banner / Vercel-bar convention).
|
|
38
43
|
// The label matches the strip's MESSAGE type ramp exactly (DM Sans
|
|
@@ -42,47 +47,59 @@ const buttonVariants = cva(
|
|
|
42
47
|
// mono-uppercase control style of "small" nor a heavier weight.
|
|
43
48
|
// Pinned h-6 across breakpoints (24px = the WCAG 2.5.8 target-size
|
|
44
49
|
// floor) with horizontal-dominant padding and a 14px glyph.
|
|
45
|
-
compact:
|
|
46
|
-
icon:
|
|
50
|
+
compact: 'py-0 px-[var(--spacing-system-sf)] text-h6 font-normal h-6 [&_svg]:h-3.5 [&_svg]:w-3.5',
|
|
51
|
+
icon: 'p-[var(--spacing-system-sf)] h-11 w-11 md:h-12 md:w-12 [&_svg]:h-4 [&_svg]:w-4 md:[&_svg]:h-6 md:[&_svg]:w-6',
|
|
47
52
|
// Quiet 32px icon target with a 16px glyph, fixed across breakpoints
|
|
48
53
|
// (Carbon ghost sm / Primer medium / shadcn icon-sm all pin 32px;
|
|
49
54
|
// ≥ the 24px WCAG 2.5.8 target floor). For icon actions that read as
|
|
50
55
|
// metadata rather than CTAs — author-page social rows, share rows.
|
|
51
56
|
// Pair with variant="transparent" for the ghost treatment.
|
|
52
|
-
|
|
57
|
+
'icon-sm': 'p-[var(--spacing-system-xxs)] h-8 w-8 [&_svg]:h-4 [&_svg]:w-4',
|
|
53
58
|
// Bare 56px media glyph (play / unmute over video). A size variant so
|
|
54
59
|
// hosts stop re-implementing a <button> to escape the 20px svg cap.
|
|
55
|
-
|
|
60
|
+
'icon-glyph': 'p-0 h-14 w-14 [&_svg]:h-14 [&_svg]:w-14',
|
|
61
|
+
},
|
|
62
|
+
// Label typography axis (Figma 133-740). `bold` is the classic h3-bold
|
|
63
|
+
// label; `regular` swaps the DEFAULT size's label to the h4 step
|
|
64
|
+
// (DM Sans 500, neutral tracking) — added for navigation buttons.
|
|
65
|
+
// `small` keeps its mono-uppercase h5 label on both fonts per the spec.
|
|
66
|
+
font: {
|
|
67
|
+
bold: '',
|
|
68
|
+
regular: '',
|
|
56
69
|
},
|
|
57
70
|
fullWidth: {
|
|
58
|
-
true:
|
|
59
|
-
false:
|
|
71
|
+
true: 'w-full',
|
|
72
|
+
false: '',
|
|
60
73
|
},
|
|
61
74
|
noPaddingX: {
|
|
62
|
-
true:
|
|
63
|
-
false:
|
|
75
|
+
true: 'px-0',
|
|
76
|
+
false: '',
|
|
64
77
|
},
|
|
65
78
|
},
|
|
66
79
|
compoundVariants: [
|
|
67
|
-
{ size:
|
|
80
|
+
{ size: 'small', class: '[&_svg]:h-4 [&_svg]:w-4' },
|
|
81
|
+
// Comes after the size classes, so cn()'s tailwind-merge resolves the
|
|
82
|
+
// ods-typography group in favour of text-h4.
|
|
83
|
+
{ size: 'default', font: 'regular', class: 'text-h4' },
|
|
68
84
|
],
|
|
69
85
|
defaultVariants: {
|
|
70
|
-
variant:
|
|
71
|
-
size:
|
|
86
|
+
variant: 'accent',
|
|
87
|
+
size: 'default',
|
|
88
|
+
font: 'bold',
|
|
72
89
|
fullWidth: false,
|
|
73
90
|
noPaddingX: false,
|
|
74
91
|
},
|
|
75
|
-
}
|
|
76
|
-
)
|
|
92
|
+
},
|
|
93
|
+
);
|
|
77
94
|
|
|
78
95
|
// Split layout (used when `splitIcon` is provided): outer button has no padding;
|
|
79
96
|
// inner slots own padding/gap so the divider can span full button height.
|
|
80
97
|
const splitShellVariants = cva(
|
|
81
98
|
[
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
'group relative inline-flex items-stretch overflow-hidden rounded-md whitespace-nowrap',
|
|
100
|
+
'transition-colors duration-200',
|
|
101
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus',
|
|
102
|
+
'disabled:pointer-events-none',
|
|
86
103
|
],
|
|
87
104
|
{
|
|
88
105
|
variants: {
|
|
@@ -94,65 +111,86 @@ const splitShellVariants = cva(
|
|
|
94
111
|
warning: buttonSurfaceClasses.warning,
|
|
95
112
|
},
|
|
96
113
|
size: {
|
|
97
|
-
default:
|
|
98
|
-
small:
|
|
114
|
+
default: 'h-12',
|
|
115
|
+
small: 'h-6 md:h-8 text-h5',
|
|
99
116
|
},
|
|
100
|
-
|
|
117
|
+
// Same label-typography axis as the normal layout — `regular` swaps the
|
|
118
|
+
// default size's h3-bold label for the h4 (DM Sans 500) step.
|
|
119
|
+
font: { bold: '', regular: '' },
|
|
120
|
+
fullWidth: { true: 'w-full', false: '' },
|
|
101
121
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
compoundVariants: [
|
|
123
|
+
{ size: 'default', font: 'bold', class: 'text-h3' },
|
|
124
|
+
{ size: 'default', font: 'regular', class: 'text-h4' },
|
|
125
|
+
],
|
|
126
|
+
defaultVariants: { variant: 'accent', size: 'default', font: 'bold', fullWidth: false },
|
|
127
|
+
},
|
|
128
|
+
);
|
|
105
129
|
|
|
106
130
|
const splitSlotVariants = cva(
|
|
107
131
|
// Glyph size lives per-size, not in the base: cva concatenates base + variant
|
|
108
132
|
// without tailwind-merge, so a base `[&_svg]:h-5` and a variant `[&_svg]:h-4`
|
|
109
133
|
// would both ship and the winner would come down to stylesheet order.
|
|
110
|
-
[
|
|
134
|
+
['inline-flex items-center justify-center', '[&_svg]:shrink-0'],
|
|
111
135
|
{
|
|
112
136
|
variants: {
|
|
113
137
|
slot: {
|
|
114
|
-
main:
|
|
115
|
-
icon:
|
|
138
|
+
main: 'gap-[var(--spacing-system-xsf)]',
|
|
139
|
+
icon: 'border-l',
|
|
116
140
|
},
|
|
117
|
-
size: { default: splitGlyphSizeClasses, small:
|
|
118
|
-
variant: { accent:
|
|
141
|
+
size: { default: splitGlyphSizeClasses, small: '[&_svg]:h-4 [&_svg]:w-4' },
|
|
142
|
+
variant: { accent: '', outline: '', transparent: '', destructive: '', warning: '' },
|
|
119
143
|
},
|
|
120
144
|
compoundVariants: [
|
|
121
|
-
{ slot:
|
|
122
|
-
{ slot:
|
|
123
|
-
{ slot:
|
|
124
|
-
{ slot:
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
{ slot: 'main', size: 'default', class: 'px-[var(--spacing-system-m)] py-[var(--spacing-system-sf)]' },
|
|
146
|
+
{ slot: 'main', size: 'small', class: 'px-[var(--spacing-system-xs)]' },
|
|
147
|
+
{ slot: 'icon', size: 'default', class: 'w-10' },
|
|
148
|
+
{ slot: 'icon', size: 'small', class: 'w-6 md:w-8' },
|
|
149
|
+
{
|
|
150
|
+
slot: 'icon',
|
|
151
|
+
variant: 'accent',
|
|
152
|
+
class: cn(
|
|
153
|
+
splitDividerColorClasses.accent,
|
|
154
|
+
'group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled',
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
{ slot: 'icon', variant: 'outline', class: splitDividerColorClasses.outline },
|
|
158
|
+
{ slot: 'icon', variant: 'transparent', class: splitDividerColorClasses.transparent },
|
|
159
|
+
{
|
|
160
|
+
slot: 'icon',
|
|
161
|
+
variant: 'destructive',
|
|
162
|
+
class: cn(
|
|
163
|
+
splitDividerColorClasses.destructive,
|
|
164
|
+
'group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled',
|
|
165
|
+
),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
slot: 'icon',
|
|
169
|
+
variant: 'warning',
|
|
170
|
+
class: cn(
|
|
171
|
+
splitDividerColorClasses.warning,
|
|
172
|
+
'group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled',
|
|
173
|
+
),
|
|
174
|
+
},
|
|
139
175
|
],
|
|
140
|
-
defaultVariants: { slot:
|
|
141
|
-
}
|
|
142
|
-
)
|
|
176
|
+
defaultVariants: { slot: 'main', size: 'default', variant: 'accent' },
|
|
177
|
+
},
|
|
178
|
+
);
|
|
143
179
|
|
|
144
180
|
/** @deprecated Use `size="small"` instead. Temporary alias kept for backward compatibility; will be removed in the future. */
|
|
145
|
-
type DeprecatedButtonSize =
|
|
181
|
+
type DeprecatedButtonSize = 'small-legacy';
|
|
146
182
|
|
|
147
|
-
type ButtonSize =
|
|
183
|
+
type ButtonSize =
|
|
184
|
+
| Exclude<VariantProps<typeof buttonVariants>['size'], 'small-legacy' | null | undefined>
|
|
185
|
+
| DeprecatedButtonSize;
|
|
148
186
|
|
|
149
187
|
interface ButtonProps
|
|
150
188
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
151
|
-
Omit<VariantProps<typeof buttonVariants>,
|
|
152
|
-
asChild?: boolean
|
|
153
|
-
href?: string
|
|
154
|
-
openInNewTab?: boolean
|
|
155
|
-
prefetch?: boolean
|
|
189
|
+
Omit<VariantProps<typeof buttonVariants>, 'size'> {
|
|
190
|
+
asChild?: boolean;
|
|
191
|
+
href?: string;
|
|
192
|
+
openInNewTab?: boolean;
|
|
193
|
+
prefetch?: boolean;
|
|
156
194
|
/**
|
|
157
195
|
* Pre-resolved anchor bundle (from `useNavLink({ href, targetPlatform })`).
|
|
158
196
|
* When set, renders the Button as `<Link>` with `href` / `target` / `rel`
|
|
@@ -161,22 +199,22 @@ interface ButtonProps
|
|
|
161
199
|
* gymnastics. Wins over the separate `href` / `openInNewTab` props.
|
|
162
200
|
*/
|
|
163
201
|
linkProps?: {
|
|
164
|
-
href: string
|
|
165
|
-
target?: '_blank'
|
|
166
|
-
rel?: 'noopener noreferrer'
|
|
167
|
-
onClick?: React.MouseEventHandler<HTMLAnchorElement
|
|
168
|
-
} | null
|
|
169
|
-
leftIcon?: React.ReactNode
|
|
170
|
-
rightIcon?: React.ReactNode
|
|
202
|
+
href: string;
|
|
203
|
+
target?: '_blank';
|
|
204
|
+
rel?: 'noopener noreferrer';
|
|
205
|
+
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
|
|
206
|
+
} | null;
|
|
207
|
+
leftIcon?: React.ReactNode;
|
|
208
|
+
rightIcon?: React.ReactNode;
|
|
171
209
|
/**
|
|
172
210
|
* Renders a vertical divider and trailing icon area inside the button.
|
|
173
211
|
* The whole button stays a single click target — the icon is decorative
|
|
174
212
|
* (`aria-hidden`). For two independent click targets, use `<SplitButton>`.
|
|
175
213
|
* Only honored when `size` is `"default"` or `"small"`.
|
|
176
214
|
*/
|
|
177
|
-
splitIcon?: React.ReactNode
|
|
178
|
-
loading?: boolean
|
|
179
|
-
size?: ButtonSize
|
|
215
|
+
splitIcon?: React.ReactNode;
|
|
216
|
+
loading?: boolean;
|
|
217
|
+
size?: ButtonSize;
|
|
180
218
|
}
|
|
181
219
|
|
|
182
220
|
const Spinner = () => (
|
|
@@ -188,13 +226,14 @@ const Spinner = () => (
|
|
|
188
226
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
189
227
|
/>
|
|
190
228
|
</svg>
|
|
191
|
-
)
|
|
229
|
+
);
|
|
192
230
|
|
|
193
231
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
194
232
|
{
|
|
195
233
|
className,
|
|
196
234
|
variant,
|
|
197
235
|
size,
|
|
236
|
+
font,
|
|
198
237
|
fullWidth,
|
|
199
238
|
noPaddingX,
|
|
200
239
|
asChild,
|
|
@@ -213,25 +252,22 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
213
252
|
},
|
|
214
253
|
ref,
|
|
215
254
|
) {
|
|
216
|
-
const isDisabled = disabled || loading
|
|
255
|
+
const isDisabled = disabled || loading;
|
|
217
256
|
|
|
218
257
|
// splitIcon is only supported for default/small sizes. With other sizes
|
|
219
258
|
// (icon, small-legacy) we silently fall back to the normal layout.
|
|
220
|
-
const useSplitLayout = !!splitIcon && (size ===
|
|
259
|
+
const useSplitLayout = !!splitIcon && (size === 'default' || size === 'small' || size === undefined);
|
|
221
260
|
|
|
222
261
|
if (useSplitLayout) {
|
|
223
|
-
const safeSize = (size ??
|
|
224
|
-
const safeVariant = (variant ??
|
|
225
|
-
const shellClasses = cn(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
)
|
|
229
|
-
const mainSlotClass = splitSlotVariants({ slot: "main", size: safeSize, variant: safeVariant })
|
|
230
|
-
const iconSlotClass = splitSlotVariants({ slot: "icon", size: safeSize, variant: safeVariant })
|
|
262
|
+
const safeSize = (size ?? 'default') as 'default' | 'small';
|
|
263
|
+
const safeVariant = (variant ?? 'accent') as 'accent' | 'outline' | 'transparent' | 'destructive' | 'warning';
|
|
264
|
+
const shellClasses = cn(splitShellVariants({ variant: safeVariant, size: safeSize, font, fullWidth }), className);
|
|
265
|
+
const mainSlotClass = splitSlotVariants({ slot: 'main', size: safeSize, variant: safeVariant });
|
|
266
|
+
const iconSlotClass = splitSlotVariants({ slot: 'icon', size: safeSize, variant: safeVariant });
|
|
231
267
|
|
|
232
268
|
const splitContent = (
|
|
233
269
|
<>
|
|
234
|
-
<span className={cn(
|
|
270
|
+
<span className={cn('contents', loading && 'invisible')}>
|
|
235
271
|
<span className={mainSlotClass}>
|
|
236
272
|
{leftIcon && <span className="inline-flex items-center">{leftIcon}</span>}
|
|
237
273
|
{children}
|
|
@@ -247,17 +283,21 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
247
283
|
</span>
|
|
248
284
|
)}
|
|
249
285
|
</>
|
|
250
|
-
)
|
|
286
|
+
);
|
|
251
287
|
|
|
252
288
|
// `linkProps` (the pre-resolved bundle from `useNavLink({ href, targetPlatform })`)
|
|
253
289
|
// wins over the legacy `href` + `openInNewTab` props so callers can thread
|
|
254
290
|
// the unified nav decision directly. Either path produces the same `<Link>`.
|
|
255
|
-
const splitAnchor =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
const splitAnchor =
|
|
292
|
+
linkProps ??
|
|
293
|
+
(href
|
|
294
|
+
? {
|
|
295
|
+
href,
|
|
296
|
+
target: openInNewTab ? ('_blank' as const) : undefined,
|
|
297
|
+
rel: openInNewTab ? ('noopener noreferrer' as const) : undefined,
|
|
298
|
+
onClick: onClick as unknown as React.MouseEventHandler<HTMLAnchorElement> | undefined,
|
|
299
|
+
}
|
|
300
|
+
: null);
|
|
261
301
|
if (splitAnchor) {
|
|
262
302
|
return (
|
|
263
303
|
<Link
|
|
@@ -271,28 +311,22 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
271
311
|
aria-label={props['aria-label']}
|
|
272
312
|
aria-disabled={isDisabled || undefined}
|
|
273
313
|
tabIndex={isDisabled ? -1 : undefined}
|
|
274
|
-
className={cn(shellClasses, isDisabled &&
|
|
314
|
+
className={cn(shellClasses, isDisabled && 'pointer-events-none')}
|
|
275
315
|
onClick={splitAnchor.onClick}
|
|
276
316
|
>
|
|
277
317
|
{splitContent}
|
|
278
318
|
</Link>
|
|
279
|
-
)
|
|
319
|
+
);
|
|
280
320
|
}
|
|
281
321
|
|
|
282
322
|
return (
|
|
283
|
-
<button
|
|
284
|
-
ref={ref}
|
|
285
|
-
className={shellClasses}
|
|
286
|
-
disabled={isDisabled}
|
|
287
|
-
onClick={onClick}
|
|
288
|
-
{...props}
|
|
289
|
-
>
|
|
323
|
+
<button ref={ref} className={shellClasses} disabled={isDisabled} onClick={onClick} {...props}>
|
|
290
324
|
{splitContent}
|
|
291
325
|
</button>
|
|
292
|
-
)
|
|
326
|
+
);
|
|
293
327
|
}
|
|
294
328
|
|
|
295
|
-
const classes = cn(buttonVariants({ variant, size, fullWidth, noPaddingX }), className)
|
|
329
|
+
const classes = cn(buttonVariants({ variant, size, font, fullWidth, noPaddingX }), className);
|
|
296
330
|
|
|
297
331
|
// asChild: consumer fully controls the rendered element; we just stamp our classes.
|
|
298
332
|
if (asChild) {
|
|
@@ -300,14 +334,14 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
300
334
|
<Slot ref={ref} className={classes} {...props}>
|
|
301
335
|
{children}
|
|
302
336
|
</Slot>
|
|
303
|
-
)
|
|
337
|
+
);
|
|
304
338
|
}
|
|
305
339
|
|
|
306
340
|
// Real content stays in layout (preserving width) but goes invisible while loading.
|
|
307
341
|
// The spinner is absolutely positioned so it never shifts the button's size.
|
|
308
342
|
const content = (
|
|
309
343
|
<>
|
|
310
|
-
<span className={cn(
|
|
344
|
+
<span className={cn('contents', loading && 'invisible')}>
|
|
311
345
|
{leftIcon && <span className="inline-flex items-center">{leftIcon}</span>}
|
|
312
346
|
{children}
|
|
313
347
|
{rightIcon && <span className="inline-flex items-center">{rightIcon}</span>}
|
|
@@ -318,15 +352,19 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
318
352
|
</span>
|
|
319
353
|
)}
|
|
320
354
|
</>
|
|
321
|
-
)
|
|
355
|
+
);
|
|
322
356
|
|
|
323
357
|
// Same `linkProps`-wins-over-href resolution as the splitIcon branch.
|
|
324
|
-
const anchor =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
const anchor =
|
|
359
|
+
linkProps ??
|
|
360
|
+
(href
|
|
361
|
+
? {
|
|
362
|
+
href,
|
|
363
|
+
target: openInNewTab ? ('_blank' as const) : undefined,
|
|
364
|
+
rel: openInNewTab ? ('noopener noreferrer' as const) : undefined,
|
|
365
|
+
onClick: onClick as unknown as React.MouseEventHandler<HTMLAnchorElement> | undefined,
|
|
366
|
+
}
|
|
367
|
+
: null);
|
|
330
368
|
if (anchor) {
|
|
331
369
|
return (
|
|
332
370
|
<Link
|
|
@@ -339,25 +377,19 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
339
377
|
aria-label={props['aria-label']}
|
|
340
378
|
aria-disabled={isDisabled || undefined}
|
|
341
379
|
tabIndex={isDisabled ? -1 : undefined}
|
|
342
|
-
className={cn(classes, isDisabled &&
|
|
380
|
+
className={cn(classes, isDisabled && 'pointer-events-none')}
|
|
343
381
|
onClick={anchor.onClick}
|
|
344
382
|
>
|
|
345
383
|
{content}
|
|
346
384
|
</Link>
|
|
347
|
-
)
|
|
385
|
+
);
|
|
348
386
|
}
|
|
349
387
|
|
|
350
388
|
return (
|
|
351
|
-
<button
|
|
352
|
-
ref={ref}
|
|
353
|
-
className={classes}
|
|
354
|
-
disabled={isDisabled}
|
|
355
|
-
onClick={onClick}
|
|
356
|
-
{...props}
|
|
357
|
-
>
|
|
389
|
+
<button ref={ref} className={classes} disabled={isDisabled} onClick={onClick} {...props}>
|
|
358
390
|
{content}
|
|
359
391
|
</button>
|
|
360
|
-
)
|
|
361
|
-
})
|
|
392
|
+
);
|
|
393
|
+
});
|
|
362
394
|
|
|
363
|
-
export { Button, buttonVariants, type ButtonProps }
|
|
395
|
+
export { Button, buttonVariants, type ButtonProps };
|
|
@@ -27,12 +27,17 @@ interface FloatingTooltipProps {
|
|
|
27
27
|
/** Disable the tooltip without unmounting the trigger wrapper. */
|
|
28
28
|
disabled?: boolean
|
|
29
29
|
/**
|
|
30
|
-
* Classes for the trigger wrapper — the
|
|
31
|
-
* `children` to anchor the tooltip. That
|
|
32
|
-
* caller's layout, so anything the child needed there (`min-w-0`, `flex-1`)
|
|
30
|
+
* Classes for the trigger wrapper — the element this component puts around
|
|
31
|
+
* `children` to anchor the tooltip. That element becomes the flex/grid item in
|
|
32
|
+
* the caller's layout, so anything the child needed there (`min-w-0`, `flex-1`)
|
|
33
33
|
* must move onto it. Omit it and the wrapper carries no class, exactly as before.
|
|
34
34
|
*/
|
|
35
35
|
triggerClassName?: string
|
|
36
|
+
/**
|
|
37
|
+
* Trigger wrapper element. Default `'div'`. Use `'span'` where a block element
|
|
38
|
+
* is invalid HTML — inside a `<p>`, a heading, or another `<span>`.
|
|
39
|
+
*/
|
|
40
|
+
as?: 'div' | 'span'
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
// Parse colored text markup like [YELLOW]text[/YELLOW] into JSX
|
|
@@ -93,6 +98,7 @@ export function FloatingTooltip({
|
|
|
93
98
|
delayDuration = 0,
|
|
94
99
|
disabled = false,
|
|
95
100
|
triggerClassName,
|
|
101
|
+
as: TriggerTag = 'div',
|
|
96
102
|
}: FloatingTooltipProps) {
|
|
97
103
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
98
104
|
const arrowRef = React.useRef<HTMLDivElement>(null)
|
|
@@ -159,9 +165,9 @@ export function FloatingTooltip({
|
|
|
159
165
|
|
|
160
166
|
return (
|
|
161
167
|
<>
|
|
162
|
-
<
|
|
168
|
+
<TriggerTag ref={refs.setReference} className={triggerClassName} {...getReferenceProps()}>
|
|
163
169
|
{children}
|
|
164
|
-
</
|
|
170
|
+
</TriggerTag>
|
|
165
171
|
<FloatingPortal>
|
|
166
172
|
{isOpen && (
|
|
167
173
|
<div
|
|
@@ -8,6 +8,9 @@ import { CheckIcon } from '../icons-v2-generated/signs-and-symbols'
|
|
|
8
8
|
import { ExternalLink } from 'lucide-react'
|
|
9
9
|
import { OpenFrameLogo } from '../..'
|
|
10
10
|
import { useCopyToClipboard } from '../../hooks/use-copy-to-clipboard'
|
|
11
|
+
import { useIsTruncated } from '../../hooks/ui/use-is-truncated'
|
|
12
|
+
import { FloatingTooltip } from './floating-tooltip'
|
|
13
|
+
import { TruncateText } from './truncate-text'
|
|
11
14
|
|
|
12
15
|
export type ServiceCardRowAction = {
|
|
13
16
|
copy?: boolean
|
|
@@ -61,9 +64,11 @@ export function ServiceCard({ title, subtitle, icon, tag, rows, className }: Ser
|
|
|
61
64
|
{resolvedIcon}
|
|
62
65
|
</div>
|
|
63
66
|
<div className="min-w-0">
|
|
64
|
-
<
|
|
67
|
+
<TruncateText variant="h3">{title}</TruncateText>
|
|
65
68
|
{subtitle && (
|
|
66
|
-
<
|
|
69
|
+
<TruncateText variant="h6" tone="secondary">
|
|
70
|
+
{subtitle}
|
|
71
|
+
</TruncateText>
|
|
67
72
|
)}
|
|
68
73
|
</div>
|
|
69
74
|
</div>
|
|
@@ -87,6 +92,7 @@ export function ServiceCard({ title, subtitle, icon, tag, rows, className }: Ser
|
|
|
87
92
|
function ServiceCardRowItem({ row }: { row: ServiceCardRow }) {
|
|
88
93
|
const [revealed, setRevealed] = useState(false)
|
|
89
94
|
const { copy, copied } = useCopyToClipboard()
|
|
95
|
+
const { ref: valueRef, isTruncated: valueTruncated } = useIsTruncated<HTMLDivElement>(row.value)
|
|
90
96
|
const actions = useMemo<ServiceCardRowAction>(() => ({ copy: true, open: !!row.href, reveal: !!row.isSecret, ...row.actions }), [row])
|
|
91
97
|
|
|
92
98
|
const displayValue = row.isSecret ? <MaskedValue value={row.value} isRevealed={revealed} /> : <span>{row.value}</span>
|
|
@@ -104,7 +110,16 @@ function ServiceCardRowItem({ row }: { row: ServiceCardRow }) {
|
|
|
104
110
|
<div className="w-20 md:w-24 shrink-0 text-h6 text-ods-text-primary">{row.label}</div>
|
|
105
111
|
)}
|
|
106
112
|
<div className={cn('flex-1 h-12 rounded-md border border-ods-border bg-ods-bg px-3 md:px-4 flex items-center justify-between min-w-0', row.monospace ? 'font-mono' : '')}>
|
|
107
|
-
|
|
113
|
+
{/* No tooltip while a secret is masked — the old native `title` leaked the raw value on hover. */}
|
|
114
|
+
<FloatingTooltip
|
|
115
|
+
content={row.value}
|
|
116
|
+
side="top"
|
|
117
|
+
disabled={!valueTruncated || (row.isSecret && !revealed)}
|
|
118
|
+
triggerClassName="min-w-0"
|
|
119
|
+
className="max-w-xs [overflow-wrap:anywhere]"
|
|
120
|
+
>
|
|
121
|
+
<div ref={valueRef} className="truncate text-ods-text-primary">{displayValue}</div>
|
|
122
|
+
</FloatingTooltip>
|
|
108
123
|
<div className="flex items-center gap-2 pl-3 flex-shrink-0">
|
|
109
124
|
{actions.reveal && (
|
|
110
125
|
<button
|
|
@@ -4,6 +4,8 @@ import React from "react"
|
|
|
4
4
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
import { XmarkCircleIcon } from "../icons-v2-generated/signs-and-symbols/xmark-circle-icon"
|
|
6
6
|
import { cn } from "../../utils/cn"
|
|
7
|
+
import { useIsTruncated } from "../../hooks/ui/use-is-truncated"
|
|
8
|
+
import { FloatingTooltip } from "./floating-tooltip"
|
|
7
9
|
|
|
8
10
|
const tagVariants = cva(
|
|
9
11
|
[
|
|
@@ -107,6 +109,37 @@ export interface TagProps
|
|
|
107
109
|
as?: 'div' | 'span'
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The tag's label slot. String labels get a `FloatingTooltip` with the full
|
|
114
|
+
* value when (and only when) the text is actually clipped — replacing the old
|
|
115
|
+
* native `title`, which showed with the OS delay, unstyled, and even when
|
|
116
|
+
* nothing was truncated. Node labels render exactly as before: the caller owns
|
|
117
|
+
* their content (and any tooltip), so none is added here.
|
|
118
|
+
* The trigger is a `span` so the tag stays valid phrasing content (`as="span"`
|
|
119
|
+
* exists precisely for tags inside a `<p>`).
|
|
120
|
+
*/
|
|
121
|
+
function TagLabel({ label, labelClassName }: { label: React.ReactNode; labelClassName?: string }) {
|
|
122
|
+
const isString = typeof label === 'string'
|
|
123
|
+
const { ref, isTruncated } = useIsTruncated<HTMLSpanElement>(isString ? label : null)
|
|
124
|
+
|
|
125
|
+
if (!isString) {
|
|
126
|
+
return <span className={cn("truncate", labelClassName)}>{label}</span>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<FloatingTooltip
|
|
131
|
+
content={label}
|
|
132
|
+
side="top"
|
|
133
|
+
disabled={!isTruncated}
|
|
134
|
+
as="span"
|
|
135
|
+
triggerClassName="min-w-0 max-w-full"
|
|
136
|
+
className="max-w-xs whitespace-pre-line [overflow-wrap:anywhere]"
|
|
137
|
+
>
|
|
138
|
+
<span ref={ref} className={cn("truncate block", labelClassName)}>{label}</span>
|
|
139
|
+
</FloatingTooltip>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
110
143
|
function Tag({
|
|
111
144
|
label,
|
|
112
145
|
variant,
|
|
@@ -134,7 +167,7 @@ function Tag({
|
|
|
134
167
|
{icon}
|
|
135
168
|
</span>
|
|
136
169
|
)}
|
|
137
|
-
<
|
|
170
|
+
<TagLabel label={label} labelClassName={labelClassName} />
|
|
138
171
|
{onClose && (
|
|
139
172
|
<button
|
|
140
173
|
type="button"
|