@flamingo-stack/openframe-frontend-core 0.0.503 → 0.0.504

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.
Files changed (120) hide show
  1. package/dist/{chunk-OAWFYZZT.js → chunk-32J4AXWO.js} +3 -3
  2. package/dist/{chunk-4UNQMYP7.cjs → chunk-5JEL52KB.cjs} +3 -3
  3. package/dist/{chunk-4UNQMYP7.cjs.map → chunk-5JEL52KB.cjs.map} +1 -1
  4. package/dist/{chunk-HR6F6IP4.js → chunk-AFHWEJXM.js} +6 -6
  5. package/dist/{chunk-3T4SMJM5.cjs → chunk-AI7ASU6V.cjs} +93 -93
  6. package/dist/{chunk-3T4SMJM5.cjs.map → chunk-AI7ASU6V.cjs.map} +1 -1
  7. package/dist/{chunk-FFXNRJ5S.cjs → chunk-C6Q3BJDJ.cjs} +2 -2
  8. package/dist/chunk-C6Q3BJDJ.cjs.map +1 -0
  9. package/dist/{chunk-72DF3QTA.cjs → chunk-CQ6O6ER2.cjs} +12 -12
  10. package/dist/{chunk-72DF3QTA.cjs.map → chunk-CQ6O6ER2.cjs.map} +1 -1
  11. package/dist/{chunk-VYEDTVIZ.js → chunk-CSRGIQ2G.js} +3 -3
  12. package/dist/{chunk-7L2RVINX.cjs → chunk-D3VTZBWC.cjs} +32 -32
  13. package/dist/{chunk-7L2RVINX.cjs.map → chunk-D3VTZBWC.cjs.map} +1 -1
  14. package/dist/{chunk-4SR2QHMB.js → chunk-EFBKOCEA.js} +3 -3
  15. package/dist/{chunk-JGXAVNZQ.js → chunk-FW2U6U4P.js} +45 -34
  16. package/dist/{chunk-JGXAVNZQ.js.map → chunk-FW2U6U4P.js.map} +1 -1
  17. package/dist/{chunk-BZF63JXP.js → chunk-GCVAZR52.js} +5 -5
  18. package/dist/{chunk-FTOVZ7O2.js → chunk-HNDHCJV4.js} +6 -6
  19. package/dist/{chunk-UCV4LFUG.cjs → chunk-IZTXVVGY.cjs} +40 -40
  20. package/dist/{chunk-UCV4LFUG.cjs.map → chunk-IZTXVVGY.cjs.map} +1 -1
  21. package/dist/{chunk-XV5SXALY.js → chunk-JCF55O7G.js} +2 -2
  22. package/dist/{chunk-CLQEBWV7.js → chunk-JQDGP4CE.js} +4 -4
  23. package/dist/{chunk-SGWPW2AO.cjs → chunk-KHE3OBHD.cjs} +18 -18
  24. package/dist/{chunk-SGWPW2AO.cjs.map → chunk-KHE3OBHD.cjs.map} +1 -1
  25. package/dist/{chunk-J4T5ZBWG.cjs → chunk-KZSQMZUL.cjs} +19 -19
  26. package/dist/{chunk-J4T5ZBWG.cjs.map → chunk-KZSQMZUL.cjs.map} +1 -1
  27. package/dist/{chunk-VO2ZONMR.js → chunk-MRPFFHBK.js} +3 -3
  28. package/dist/{chunk-JIZJDAXI.cjs → chunk-NNQMA3N6.cjs} +12 -12
  29. package/dist/{chunk-JIZJDAXI.cjs.map → chunk-NNQMA3N6.cjs.map} +1 -1
  30. package/dist/{chunk-AWDHRIL3.cjs → chunk-OK34RZWT.cjs} +73 -73
  31. package/dist/{chunk-AWDHRIL3.cjs.map → chunk-OK34RZWT.cjs.map} +1 -1
  32. package/dist/{chunk-2OXJ6NHI.cjs → chunk-Q6RMV5WK.cjs} +5 -5
  33. package/dist/{chunk-2OXJ6NHI.cjs.map → chunk-Q6RMV5WK.cjs.map} +1 -1
  34. package/dist/{chunk-G43CEISB.js → chunk-QFQBYNQJ.js} +4 -4
  35. package/dist/{chunk-WHJGRRK2.js → chunk-QW7SW5HY.js} +52 -43
  36. package/dist/chunk-QW7SW5HY.js.map +1 -0
  37. package/dist/{chunk-L5CI57RM.cjs → chunk-T3BF6JTQ.cjs} +52 -43
  38. package/dist/chunk-T3BF6JTQ.cjs.map +1 -0
  39. package/dist/{chunk-RR3Z52X5.cjs → chunk-US2F5GJO.cjs} +40 -40
  40. package/dist/{chunk-RR3Z52X5.cjs.map → chunk-US2F5GJO.cjs.map} +1 -1
  41. package/dist/{chunk-5RIHLXEP.cjs → chunk-WXARO2KA.cjs} +33 -33
  42. package/dist/{chunk-5RIHLXEP.cjs.map → chunk-WXARO2KA.cjs.map} +1 -1
  43. package/dist/{chunk-DMXB3VE2.cjs → chunk-X5USQ6UC.cjs} +492 -481
  44. package/dist/chunk-X5USQ6UC.cjs.map +1 -0
  45. package/dist/{chunk-SFJRMBJC.js → chunk-XRGAQM3F.js} +2 -2
  46. package/dist/{chunk-VCPTU4IW.js → chunk-XT4P4JGJ.js} +6 -6
  47. package/dist/{chunk-F7AD5CUU.js → chunk-YKF25XRY.js} +2 -2
  48. package/dist/chunk-YKF25XRY.js.map +1 -0
  49. package/dist/components/case-studies/index.cjs +10 -10
  50. package/dist/components/case-studies/index.js +4 -4
  51. package/dist/components/chat/index.cjs +4 -4
  52. package/dist/components/chat/index.js +3 -3
  53. package/dist/components/contact/index.cjs +5 -5
  54. package/dist/components/contact/index.js +4 -4
  55. package/dist/components/docs/index.cjs +7 -7
  56. package/dist/components/docs/index.js +6 -6
  57. package/dist/components/embeds/index.cjs +5 -5
  58. package/dist/components/embeds/index.js +4 -4
  59. package/dist/components/faq/index.cjs +6 -6
  60. package/dist/components/faq/index.js +5 -5
  61. package/dist/components/features/index.cjs +4 -4
  62. package/dist/components/features/index.js +3 -3
  63. package/dist/components/help-center-pages/index.cjs +23 -23
  64. package/dist/components/help-center-pages/index.js +14 -14
  65. package/dist/components/index.cjs +161 -161
  66. package/dist/components/index.js +12 -12
  67. package/dist/components/navigation/header-button.d.ts.map +1 -1
  68. package/dist/components/navigation/header.d.ts.map +1 -1
  69. package/dist/components/navigation/index.cjs +4 -4
  70. package/dist/components/navigation/index.js +3 -3
  71. package/dist/components/navigation/mingo-ai-button.d.ts.map +1 -1
  72. package/dist/components/navigation/top-navigation.d.ts +5 -1
  73. package/dist/components/navigation/top-navigation.d.ts.map +1 -1
  74. package/dist/components/onboarding-guides/index.cjs +8 -8
  75. package/dist/components/onboarding-guides/index.js +7 -7
  76. package/dist/components/related-content/index.cjs +6 -6
  77. package/dist/components/related-content/index.js +5 -5
  78. package/dist/components/tickets/index.cjs +7 -7
  79. package/dist/components/tickets/index.js +6 -6
  80. package/dist/components/ui/button/button.d.ts +6 -5
  81. package/dist/components/ui/button/button.d.ts.map +1 -1
  82. package/dist/components/ui/file-manager/index.cjs +50 -50
  83. package/dist/components/ui/file-manager/index.js +1 -1
  84. package/dist/components/ui/index.cjs +4 -4
  85. package/dist/components/ui/index.js +3 -3
  86. package/dist/hooks/index.cjs +2 -2
  87. package/dist/hooks/index.js +1 -1
  88. package/dist/hooks/ui/use-header-height.d.ts.map +1 -1
  89. package/dist/index.cjs +4 -4
  90. package/dist/index.js +3 -3
  91. package/package.json +1 -1
  92. package/src/components/features/time-tracker/time-tracker-header-button.tsx +1 -1
  93. package/src/components/navigation/app-header.tsx +10 -10
  94. package/src/components/navigation/header-button.tsx +4 -1
  95. package/src/components/navigation/header-mingo-button.tsx +2 -2
  96. package/src/components/navigation/header-skeleton.tsx +4 -4
  97. package/src/components/navigation/header.tsx +20 -14
  98. package/src/components/navigation/mingo-ai-button.tsx +23 -22
  99. package/src/components/navigation/mobile-burger-menu.tsx +3 -3
  100. package/src/components/navigation/top-navigation.tsx +17 -1
  101. package/src/components/ui/button/button.tsx +165 -133
  102. package/src/hooks/ui/use-header-height.ts +3 -1
  103. package/src/stories/TopNavigation.stories.tsx +20 -1
  104. package/dist/chunk-DMXB3VE2.cjs.map +0 -1
  105. package/dist/chunk-F7AD5CUU.js.map +0 -1
  106. package/dist/chunk-FFXNRJ5S.cjs.map +0 -1
  107. package/dist/chunk-L5CI57RM.cjs.map +0 -1
  108. package/dist/chunk-WHJGRRK2.js.map +0 -1
  109. /package/dist/{chunk-OAWFYZZT.js.map → chunk-32J4AXWO.js.map} +0 -0
  110. /package/dist/{chunk-HR6F6IP4.js.map → chunk-AFHWEJXM.js.map} +0 -0
  111. /package/dist/{chunk-VYEDTVIZ.js.map → chunk-CSRGIQ2G.js.map} +0 -0
  112. /package/dist/{chunk-4SR2QHMB.js.map → chunk-EFBKOCEA.js.map} +0 -0
  113. /package/dist/{chunk-BZF63JXP.js.map → chunk-GCVAZR52.js.map} +0 -0
  114. /package/dist/{chunk-FTOVZ7O2.js.map → chunk-HNDHCJV4.js.map} +0 -0
  115. /package/dist/{chunk-XV5SXALY.js.map → chunk-JCF55O7G.js.map} +0 -0
  116. /package/dist/{chunk-CLQEBWV7.js.map → chunk-JQDGP4CE.js.map} +0 -0
  117. /package/dist/{chunk-VO2ZONMR.js.map → chunk-MRPFFHBK.js.map} +0 -0
  118. /package/dist/{chunk-G43CEISB.js.map → chunk-QFQBYNQJ.js.map} +0 -0
  119. /package/dist/{chunk-SFJRMBJC.js.map → chunk-XRGAQM3F.js.map} +0 -0
  120. /package/dist/{chunk-VCPTU4IW.js.map → chunk-XT4P4JGJ.js.map} +0 -0
@@ -1,22 +1,27 @@
1
- "use client"
1
+ 'use client';
2
2
 
3
- import { Slot } from "@radix-ui/react-slot"
4
- import { cva, type VariantProps } from "class-variance-authority"
5
- import Link from "../../../embed-shims/next-link"
6
- import React from "react"
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 "../../../utils/cn"
9
- import { buttonSurfaceClasses, outlineBorderClasses, splitDividerColorClasses, splitGlyphSizeClasses } from "./button-styles"
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
- "relative inline-flex items-center justify-center gap-[var(--spacing-system-xsf)]",
15
- "rounded-md whitespace-nowrap",
16
- "transition-colors duration-200",
17
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus",
18
- "disabled:pointer-events-none",
19
- "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:h-5 [&_svg]:w-5",
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: "py-[var(--spacing-system-sf)] px-[var(--spacing-system-m)] text-h3 md:h-12 h-11",
34
- small: "p-[var(--spacing-system-xs)] text-h5 h-6 md:h-8",
35
- "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
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: "py-0 px-[var(--spacing-system-sf)] text-h6 font-normal h-6 [&_svg]:h-3.5 [&_svg]:w-3.5",
46
- 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",
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
- "icon-sm": "p-[var(--spacing-system-xxs)] h-8 w-8 [&_svg]:h-4 [&_svg]:w-4",
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
- "icon-glyph": "p-0 h-14 w-14 [&_svg]:h-14 [&_svg]:w-14",
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: "w-full",
59
- false: "",
71
+ true: 'w-full',
72
+ false: '',
60
73
  },
61
74
  noPaddingX: {
62
- true: "px-0",
63
- false: "",
75
+ true: 'px-0',
76
+ false: '',
64
77
  },
65
78
  },
66
79
  compoundVariants: [
67
- { size: "small", class: "[&_svg]:h-4 [&_svg]:w-4" },
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: "accent",
71
- size: "default",
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
- "group relative inline-flex items-stretch overflow-hidden rounded-md whitespace-nowrap",
83
- "transition-colors duration-200",
84
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus",
85
- "disabled:pointer-events-none",
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: "h-12 text-h3",
98
- small: "h-6 md:h-8 text-h5",
114
+ default: 'h-12',
115
+ small: 'h-6 md:h-8 text-h5',
99
116
  },
100
- fullWidth: { true: "w-full", false: "" },
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
- defaultVariants: { variant: "accent", size: "default", fullWidth: false },
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
- ["inline-flex items-center justify-center", "[&_svg]:shrink-0"],
134
+ ['inline-flex items-center justify-center', '[&_svg]:shrink-0'],
111
135
  {
112
136
  variants: {
113
137
  slot: {
114
- main: "gap-[var(--spacing-system-xsf)]",
115
- icon: "border-l",
138
+ main: 'gap-[var(--spacing-system-xsf)]',
139
+ icon: 'border-l',
116
140
  },
117
- size: { default: splitGlyphSizeClasses, small: "[&_svg]:h-4 [&_svg]:w-4" },
118
- variant: { accent: "", outline: "", transparent: "", destructive: "", warning: "" },
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: "main", size: "default", class: "px-[var(--spacing-system-m)] py-[var(--spacing-system-sf)]" },
122
- { slot: "main", size: "small", class: "px-[var(--spacing-system-xs)]" },
123
- { slot: "icon", size: "default", class: "w-10" },
124
- { slot: "icon", size: "small", class: "w-6 md:w-8" },
125
- { slot: "icon", variant: "accent", class: cn(
126
- splitDividerColorClasses.accent,
127
- "group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled",
128
- ) },
129
- { slot: "icon", variant: "outline", class: splitDividerColorClasses.outline },
130
- { slot: "icon", variant: "transparent", class: splitDividerColorClasses.transparent },
131
- { slot: "icon", variant: "destructive", class: cn(
132
- splitDividerColorClasses.destructive,
133
- "group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled",
134
- ) },
135
- { slot: "icon", variant: "warning", class: cn(
136
- splitDividerColorClasses.warning,
137
- "group-disabled:border-ods-disabled group-aria-disabled:border-ods-disabled",
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: "main", size: "default", variant: "accent" },
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 = "small-legacy"
181
+ type DeprecatedButtonSize = 'small-legacy';
146
182
 
147
- type ButtonSize = Exclude<VariantProps<typeof buttonVariants>["size"], "small-legacy" | null | undefined> | DeprecatedButtonSize
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>, "size"> {
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 === "default" || size === "small" || size === undefined)
259
+ const useSplitLayout = !!splitIcon && (size === 'default' || size === 'small' || size === undefined);
221
260
 
222
261
  if (useSplitLayout) {
223
- const safeSize = (size ?? "default") as "default" | "small"
224
- const safeVariant = (variant ?? "accent") as "accent" | "outline" | "transparent" | "destructive" | "warning"
225
- const shellClasses = cn(
226
- splitShellVariants({ variant: safeVariant, size: safeSize, fullWidth }),
227
- className,
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("contents", loading && "invisible")}>
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 = linkProps ?? (href ? {
256
- href,
257
- target: openInNewTab ? '_blank' as const : undefined,
258
- rel: openInNewTab ? 'noopener noreferrer' as const : undefined,
259
- onClick: onClick as unknown as React.MouseEventHandler<HTMLAnchorElement> | undefined,
260
- } : null)
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 && "pointer-events-none")}
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("contents", loading && "invisible")}>
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 = linkProps ?? (href ? {
325
- href,
326
- target: openInNewTab ? '_blank' as const : undefined,
327
- rel: openInNewTab ? 'noopener noreferrer' as const : undefined,
328
- onClick: onClick as unknown as React.MouseEventHandler<HTMLAnchorElement> | undefined,
329
- } : null)
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 && "pointer-events-none")}
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 };
@@ -9,7 +9,9 @@ import { useEffect, useState } from 'react'
9
9
  * overlap the header.
10
10
  */
11
11
  export function useHeaderHeight(
12
- defaultHeight = 64,
12
+ // First-paint fallback = the unified top-navigation `big` bar (72px), the
13
+ // size every hub/marketing site renders; replaced by the live measurement.
14
+ defaultHeight = 72,
13
15
  options: {
14
16
  /**
15
17
  * When false, no observers are attached and the default height is
@@ -23,7 +23,7 @@ const logo = (
23
23
  const navLinks = (
24
24
  <nav className="flex items-center gap-2" aria-label="Main navigation">
25
25
  {['OpenFrame', 'OpenMSP', 'Resources', 'Pricing'].map(label => (
26
- <Button key={label} variant="transparent" size="small-legacy" className="text-h3 font-bold tracking-[-0.36px]">
26
+ <Button key={label} variant="transparent" size="default" font="regular">
27
27
  {label}
28
28
  </Button>
29
29
  ))}
@@ -79,6 +79,7 @@ const meta = {
79
79
  tags: ['autodocs'],
80
80
  argTypes: {
81
81
  centerBreakpoint: { control: 'radio', options: ['md', 'lg'] },
82
+ size: { control: 'radio', options: ['small', 'big'] },
82
83
  backgroundClassName: { control: 'text' },
83
84
  mobileTopBorder: { control: 'boolean' },
84
85
  },
@@ -115,6 +116,24 @@ export const MarketingFull: Story = {
115
116
  },
116
117
  };
117
118
 
119
+ /**
120
+ * The `big` (72px) bar every hub/marketing site renders — same zones, taller
121
+ * bar and 72px cells (Figma 2936-6812). `small` (56px) is the console size.
122
+ */
123
+ export const MarketingBig: Story = {
124
+ args: {
125
+ ...MarketingFull.args,
126
+ size: 'big',
127
+ // Big bar carries the full default-size CTA (Figma 2936-6812), not the
128
+ // 32px mono control.
129
+ cta: (
130
+ <Button variant="accent" onClick={fn()}>
131
+ Try for Free
132
+ </Button>
133
+ ),
134
+ },
135
+ };
136
+
118
137
  /**
119
138
  * Console-style arrangement: no logo/nav (the app sidebar owns branding),
120
139
  * `centerBreakpoint="md"` so the center zone (here just the spacer) starts at