@flamingo-stack/openframe-frontend-core 0.0.202 → 0.0.203
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-OII2IERE.cjs → chunk-25LVV26X.cjs} +4 -4
- package/dist/chunk-25LVV26X.cjs.map +1 -0
- package/dist/{chunk-JIKTMXTZ.cjs → chunk-3YH2M76N.cjs} +896 -740
- package/dist/chunk-3YH2M76N.cjs.map +1 -0
- package/dist/{chunk-55HF462A.js → chunk-CPXLQ57U.js} +6 -7
- package/dist/chunk-CPXLQ57U.js.map +1 -0
- package/dist/{chunk-IDULPYOU.js → chunk-E6Q6UGDK.js} +1897 -1741
- package/dist/chunk-E6Q6UGDK.js.map +1 -0
- package/dist/{chunk-3B43AHYE.cjs → chunk-RMB5DVED.cjs} +6 -7
- package/dist/chunk-RMB5DVED.cjs.map +1 -0
- package/dist/{chunk-4ML3NA2L.js → chunk-XGL5FKIK.js} +4 -4
- package/dist/chunk-XGL5FKIK.js.map +1 -0
- package/dist/components/chat/chat-ticket-item.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/features/select-button.d.ts.map +1 -1
- package/dist/components/index.cjs +6 -4
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +5 -3
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/resizable.d.ts +1 -1
- package/dist/components/ui/button/split-button.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-row.d.ts +16 -4
- package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +52 -52
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +3 -3
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/floating-tooltip.d.ts +3 -1
- package/dist/components/ui/floating-tooltip.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +6 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -3
- package/dist/components/ui/input-trigger.d.ts.map +1 -1
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/ticket-info-section.d.ts.map +1 -1
- package/dist/components/ui/ticket-note-card.d.ts.map +1 -1
- package/dist/components/ui/truncate-text.d.ts +33 -0
- package/dist/components/ui/truncate-text.d.ts.map +1 -0
- package/dist/components/user-summary-stub.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/components/chat/chat-container.tsx +2 -2
- package/src/components/chat/chat-ticket-item.tsx +2 -3
- package/src/components/features/board/ticket-card.tsx +2 -2
- package/src/components/features/filters-dropdown.tsx +1 -1
- package/src/components/features/notifications/notification-tile.tsx +2 -2
- package/src/components/features/policy-configuration-panel.tsx +1 -1
- package/src/components/features/push-button-selector.tsx +1 -1
- package/src/components/features/select-button.tsx +2 -3
- package/src/components/features/video-bites-display.tsx +1 -1
- package/src/components/features/waitlist-form.tsx +1 -1
- package/src/components/filter-chip.tsx +1 -1
- package/src/components/layout/title-block.tsx +2 -2
- package/src/components/navigation/header-organization-filter.tsx +1 -1
- package/src/components/navigation/navigation-sidebar.tsx +107 -54
- package/src/components/platform/ScriptInfoSection.tsx +1 -1
- package/src/components/shared/onboarding/onboarding-step-card.tsx +2 -2
- package/src/components/shared/product-release/product-release-card.tsx +6 -6
- package/src/components/shared/product-release/release-detail-page.tsx +1 -1
- package/src/components/ui/assignee-dropdown.tsx +3 -3
- package/src/components/ui/autocomplete.tsx +2 -2
- package/src/components/ui/button/split-button.tsx +3 -5
- package/src/components/ui/checkbox-block.tsx +1 -1
- package/src/components/ui/data-table/data-table-row.tsx +82 -48
- package/src/components/ui/device-card-compact.tsx +2 -2
- package/src/components/ui/device-card.tsx +2 -2
- package/src/components/ui/entity-image.tsx +1 -1
- package/src/components/ui/field-wrapper.tsx +1 -1
- package/src/components/ui/file-manager/file-manager-table-row.tsx +2 -2
- package/src/components/ui/file-upload.tsx +2 -2
- package/src/components/ui/filter-list.tsx +1 -1
- package/src/components/ui/floating-tooltip.tsx +9 -5
- package/src/components/ui/hidden-tags-popup.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/info-card.tsx +2 -2
- package/src/components/ui/input-trigger.tsx +1 -2
- package/src/components/ui/organization-card.tsx +3 -3
- package/src/components/ui/radio-group.tsx +2 -3
- package/src/components/ui/search-input.tsx +2 -2
- package/src/components/ui/service-card.tsx +3 -3
- package/src/components/ui/tag.tsx +1 -1
- package/src/components/ui/tags-manager.tsx +2 -2
- package/src/components/ui/ticket-attachments-list.tsx +1 -1
- package/src/components/ui/ticket-info-section.tsx +2 -3
- package/src/components/ui/ticket-note-card.tsx +4 -1
- package/src/components/ui/toaster.tsx +3 -3
- package/src/components/ui/truncate-text.tsx +116 -0
- package/src/components/user-summary-stub.tsx +32 -26
- package/src/components/vendor-display-button.tsx +1 -1
- package/src/stories/SplitButton.stories.tsx +7 -1
- package/dist/chunk-3B43AHYE.cjs.map +0 -1
- package/dist/chunk-4ML3NA2L.js.map +0 -1
- package/dist/chunk-55HF462A.js.map +0 -1
- package/dist/chunk-IDULPYOU.js.map +0 -1
- package/dist/chunk-JIKTMXTZ.cjs.map +0 -1
- package/dist/chunk-OII2IERE.cjs.map +0 -1
|
@@ -23,6 +23,8 @@ interface FloatingTooltipProps {
|
|
|
23
23
|
side?: "top" | "right" | "bottom" | "left"
|
|
24
24
|
className?: string
|
|
25
25
|
delayDuration?: number
|
|
26
|
+
/** Disable the tooltip without unmounting the trigger wrapper. */
|
|
27
|
+
disabled?: boolean
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// Parse colored text markup like [YELLOW]text[/YELLOW] into JSX
|
|
@@ -75,12 +77,13 @@ function parseColoredText(text: string): React.ReactNode {
|
|
|
75
77
|
return parts.length > 0 ? <>{parts}</> : text
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
export function FloatingTooltip({
|
|
79
|
-
content,
|
|
80
|
-
children,
|
|
81
|
-
side = "right",
|
|
80
|
+
export function FloatingTooltip({
|
|
81
|
+
content,
|
|
82
|
+
children,
|
|
83
|
+
side = "right",
|
|
82
84
|
className,
|
|
83
|
-
delayDuration = 0
|
|
85
|
+
delayDuration = 0,
|
|
86
|
+
disabled = false,
|
|
84
87
|
}: FloatingTooltipProps) {
|
|
85
88
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
86
89
|
const arrowRef = React.useRef<HTMLDivElement>(null)
|
|
@@ -104,6 +107,7 @@ export function FloatingTooltip({
|
|
|
104
107
|
|
|
105
108
|
const hover = useHover(context, {
|
|
106
109
|
move: false,
|
|
110
|
+
enabled: !disabled,
|
|
107
111
|
delay: { open: delayDuration, close: 0 },
|
|
108
112
|
handleClose: safePolygon(),
|
|
109
113
|
})
|
|
@@ -40,7 +40,7 @@ export const HiddenTagsPopup = forwardRef(function HiddenTagsPopup(
|
|
|
40
40
|
"border-b border-ods-border last:border-b-0",
|
|
41
41
|
)}
|
|
42
42
|
>
|
|
43
|
-
<span className="text-h5 truncate uppercase text-ods-text-primary">
|
|
43
|
+
<span className="text-h5 truncate uppercase text-ods-text-primary" title={typeof item.label === 'string' ? item.label : undefined}>
|
|
44
44
|
{item.label}
|
|
45
45
|
</span>
|
|
46
46
|
{!disabled && onRemove && (
|
|
@@ -76,6 +76,7 @@ export * from './entity-image'
|
|
|
76
76
|
export * from './feature-card'
|
|
77
77
|
export * from './feature-list'
|
|
78
78
|
export { FloatingTooltip } from './floating-tooltip'
|
|
79
|
+
export { TruncateText, type TruncateTextProps } from './truncate-text'
|
|
79
80
|
export * from './highlight-card'
|
|
80
81
|
export * from './icons-block'
|
|
81
82
|
export * from './filter-modal'
|
|
@@ -39,7 +39,7 @@ export function InfoCard({ data, className = '' }: InfoCardProps) {
|
|
|
39
39
|
>
|
|
40
40
|
{data.title && (
|
|
41
41
|
<div className="flex items-center gap-[var(--spacing-system-xsf)] self-stretch h-6">
|
|
42
|
-
<span className="text-h4 text-ods-text-primary truncate">
|
|
42
|
+
<span className="text-h4 text-ods-text-primary truncate" title={data.title}>
|
|
43
43
|
{data.title}
|
|
44
44
|
</span>
|
|
45
45
|
{data.icon}
|
|
@@ -48,7 +48,7 @@ export function InfoCard({ data, className = '' }: InfoCardProps) {
|
|
|
48
48
|
|
|
49
49
|
{/* Subtitle */}
|
|
50
50
|
{data.subtitle && (
|
|
51
|
-
<div className="text-h4 text-ods-text-secondary truncate self-stretch">
|
|
51
|
+
<div className="text-h4 text-ods-text-secondary truncate self-stretch" title={data.subtitle}>
|
|
52
52
|
{data.subtitle}
|
|
53
53
|
</div>
|
|
54
54
|
)}
|
|
@@ -66,8 +66,7 @@ export const InputTrigger = React.forwardRef<HTMLButtonElement, InputTriggerProp
|
|
|
66
66
|
className={cn(
|
|
67
67
|
"flex-1 min-w-0 text-left truncate",
|
|
68
68
|
isPlaceholder && "text-ods-text-secondary",
|
|
69
|
-
)}
|
|
70
|
-
>
|
|
69
|
+
)} title={isPlaceholder ? placeholder : (typeof selectedLabel === 'string' ? selectedLabel : undefined)}>
|
|
71
70
|
{isPlaceholder ? placeholder : selectedLabel}
|
|
72
71
|
</span>
|
|
73
72
|
{endIcon && (
|
|
@@ -104,10 +104,10 @@ export function OrganizationCard({
|
|
|
104
104
|
/>
|
|
105
105
|
|
|
106
106
|
<div className="flex-1 flex flex-col justify-center py-2 min-w-0">
|
|
107
|
-
<h3 className="font-['DM_Sans'] font-bold text-lg leading-[1.33] tracking-[-0.02em] text-ods-text-primary transition-colors truncate">
|
|
107
|
+
<h3 className="font-['DM_Sans'] font-bold text-lg leading-[1.33] tracking-[-0.02em] text-ods-text-primary transition-colors truncate" title={organization.name}>
|
|
108
108
|
{organization.name}
|
|
109
109
|
</h3>
|
|
110
|
-
<p className="font-['DM_Sans'] font-medium text-sm leading-[1.43] text-ods-text-secondary truncate">
|
|
110
|
+
<p className="font-['DM_Sans'] font-medium text-sm leading-[1.43] text-ods-text-secondary truncate" title={organization.industry || organization.tier || organization.websiteUrl || "Organization"}>
|
|
111
111
|
{organization.industry || organization.tier || organization.websiteUrl || "Organization"}
|
|
112
112
|
</p>
|
|
113
113
|
</div>
|
|
@@ -116,7 +116,7 @@ export function OrganizationCard({
|
|
|
116
116
|
{/* Description */}
|
|
117
117
|
{organization.description && (
|
|
118
118
|
<div className="w-full h-12 overflow-hidden">
|
|
119
|
-
<p className="font-['DM_Sans'] font-medium text-lg leading-[1.33] text-ods-text-primary line-clamp-2">
|
|
119
|
+
<p className="font-['DM_Sans'] font-medium text-lg leading-[1.33] text-ods-text-primary line-clamp-2" title={organization.description}>
|
|
120
120
|
{organization.description}
|
|
121
121
|
</p>
|
|
122
122
|
</div>
|
|
@@ -145,8 +145,7 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
145
145
|
className={cn(
|
|
146
146
|
"font-[family-name:var(--font-h4-family)] font-[number:var(--font-h4-weight)] text-[length:var(--font-size-h4-body)] leading-[24px]",
|
|
147
147
|
"text-ods-text-primary select-none truncate"
|
|
148
|
-
)}
|
|
149
|
-
>
|
|
148
|
+
)} title={typeof option.label === 'string' ? option.label : undefined}>
|
|
150
149
|
{option.label}
|
|
151
150
|
</span>
|
|
152
151
|
{option.description && (
|
|
@@ -168,7 +167,7 @@ const RadioGroupBlock = React.forwardRef<
|
|
|
168
167
|
})}
|
|
169
168
|
</RadioGroupPrimitive.Root>
|
|
170
169
|
{error && (
|
|
171
|
-
<p className="absolute bottom-0 left-0 right-0 translate-y-full text-h6 truncate text-ods-error">
|
|
170
|
+
<p className="absolute bottom-0 left-0 right-0 translate-y-full text-h6 truncate text-ods-error" title={error}>
|
|
172
171
|
{error}
|
|
173
172
|
</p>
|
|
174
173
|
)}
|
|
@@ -325,11 +325,11 @@ export function SearchInput({
|
|
|
325
325
|
<div className={cn(
|
|
326
326
|
"text-sm font-medium leading-5 truncate",
|
|
327
327
|
isHighlighted ? "text-ods-accent" : "text-ods-text-primary"
|
|
328
|
-
)}>
|
|
328
|
+
)} title={result.title}>
|
|
329
329
|
{result.title}
|
|
330
330
|
</div>
|
|
331
331
|
{result.description && (
|
|
332
|
-
<div className="text-xs leading-4 text-ods-text-secondary truncate mt-0.5">
|
|
332
|
+
<div className="text-xs leading-4 text-ods-text-secondary truncate mt-0.5" title={result.description}>
|
|
333
333
|
{result.description}
|
|
334
334
|
</div>
|
|
335
335
|
)}
|
|
@@ -61,9 +61,9 @@ export function ServiceCard({ title, subtitle, icon, tag, rows, className }: Ser
|
|
|
61
61
|
{resolvedIcon}
|
|
62
62
|
</div>
|
|
63
63
|
<div className="min-w-0">
|
|
64
|
-
<div className="text-xl font-semibold text-ods-text-primary truncate">{title}</div>
|
|
64
|
+
<div className="text-xl font-semibold text-ods-text-primary truncate" title={title}>{title}</div>
|
|
65
65
|
{subtitle && (
|
|
66
|
-
<div className="text-sm text-ods-text-secondary truncate">{subtitle}</div>
|
|
66
|
+
<div className="text-sm text-ods-text-secondary truncate" title={subtitle}>{subtitle}</div>
|
|
67
67
|
)}
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
@@ -104,7 +104,7 @@ function ServiceCardRowItem({ row }: { row: ServiceCardRow }) {
|
|
|
104
104
|
<div className="w-20 md:w-24 shrink-0 text-sm font-medium text-ods-text-primary">{row.label}</div>
|
|
105
105
|
)}
|
|
106
106
|
<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
|
-
<div className="truncate text-ods-text-primary min-w-0">{displayValue}</div>
|
|
107
|
+
<div className="truncate text-ods-text-primary min-w-0" title={typeof row.value === 'string' ? row.value : undefined}>{displayValue}</div>
|
|
108
108
|
<div className="flex items-center gap-2 pl-3 flex-shrink-0">
|
|
109
109
|
{actions.reveal && (
|
|
110
110
|
<button
|
|
@@ -92,7 +92,7 @@ function Tag({
|
|
|
92
92
|
{icon}
|
|
93
93
|
</span>
|
|
94
94
|
)}
|
|
95
|
-
<span className={cn("truncate", labelClassName)}>{label}</span>
|
|
95
|
+
<span className={cn("truncate", labelClassName)} title={typeof label === 'string' ? label : undefined}>{label}</span>
|
|
96
96
|
{onClose && (
|
|
97
97
|
<button
|
|
98
98
|
type="button"
|
|
@@ -329,7 +329,7 @@ export function TagsManager({
|
|
|
329
329
|
}}
|
|
330
330
|
>
|
|
331
331
|
<div className="flex items-center justify-between w-full">
|
|
332
|
-
<span className="truncate">{tag.name}</span>
|
|
332
|
+
<span className="truncate" title={tag.name}>{tag.name}</span>
|
|
333
333
|
<div className="flex items-center gap-1 shrink-0">
|
|
334
334
|
{isSelected && (
|
|
335
335
|
<CheckIcon
|
|
@@ -401,7 +401,7 @@ export function TagsManager({
|
|
|
401
401
|
size={16}
|
|
402
402
|
className="text-ods-accent shrink-0"
|
|
403
403
|
/>
|
|
404
|
-
<span className="text-ods-accent truncate">
|
|
404
|
+
<span className="text-ods-accent truncate" title={`Create "${search.trim()}"`}>
|
|
405
405
|
Create “{search.trim()}”
|
|
406
406
|
</span>
|
|
407
407
|
</div>
|
|
@@ -44,7 +44,7 @@ export function TicketAttachmentsList({ attachments, className }: TicketAttachme
|
|
|
44
44
|
</div>
|
|
45
45
|
)}
|
|
46
46
|
<div className="flex-1 min-w-0 overflow-hidden">
|
|
47
|
-
<p className="text-h4 text-ods-text-primary truncate">{attachment.fileName}</p>
|
|
47
|
+
<p className="text-h4 text-ods-text-primary truncate" title={attachment.fileName}>{attachment.fileName}</p>
|
|
48
48
|
<p className="text-h6 text-ods-text-secondary">{attachment.fileSize}</p>
|
|
49
49
|
</div>
|
|
50
50
|
{attachment.onDownload && (
|
|
@@ -89,12 +89,11 @@ function InfoCell({ value, label, icon, onClick }: {
|
|
|
89
89
|
<button
|
|
90
90
|
type="button"
|
|
91
91
|
onClick={onClick}
|
|
92
|
-
className="text-h4 text-ods-text-primary truncate hover:text-ods-accent transition-colors cursor-pointer text-left"
|
|
93
|
-
>
|
|
92
|
+
className="text-h4 text-ods-text-primary truncate hover:text-ods-accent transition-colors cursor-pointer text-left" title={value}>
|
|
94
93
|
{value}
|
|
95
94
|
</button>
|
|
96
95
|
) : (
|
|
97
|
-
<span className="text-h4 text-ods-text-primary truncate">{value}</span>
|
|
96
|
+
<span className="text-h4 text-ods-text-primary truncate" title={value}>{value}</span>
|
|
98
97
|
)}
|
|
99
98
|
</div>
|
|
100
99
|
<span className="text-h6 text-ods-text-secondary truncate">{label}</span>
|
|
@@ -96,7 +96,10 @@ export function TicketNoteCard({ note, onEdit, onDelete, className }: TicketNote
|
|
|
96
96
|
) : (
|
|
97
97
|
<>
|
|
98
98
|
<p className="text-h4 text-ods-text-primary">{note.text}</p>
|
|
99
|
-
<p
|
|
99
|
+
<p
|
|
100
|
+
className="text-h6 text-ods-text-secondary truncate"
|
|
101
|
+
title={`${note.authorName} • ${note.createdAt}`}
|
|
102
|
+
>
|
|
100
103
|
{note.authorName} • {note.createdAt}
|
|
101
104
|
</p>
|
|
102
105
|
</>
|
|
@@ -61,10 +61,10 @@ function ToastHeader({
|
|
|
61
61
|
|
|
62
62
|
<div className="flex min-w-0 flex-1 flex-col justify-center font-['DM_Sans'] font-medium">
|
|
63
63
|
{title ? (
|
|
64
|
-
<p className="truncate pr-5 text-[18px] leading-6 text-ods-text-primary">{title}</p>
|
|
64
|
+
<p className="truncate pr-5 text-[18px] leading-6 text-ods-text-primary" title={typeof title === 'string' ? title : undefined}>{title}</p>
|
|
65
65
|
) : null}
|
|
66
66
|
{description ? (
|
|
67
|
-
<p className="text-[14px] leading-5 text-ods-text-secondary line-clamp-3">{description}</p>
|
|
67
|
+
<p className="text-[14px] leading-5 text-ods-text-secondary line-clamp-3" title={typeof description === 'string' ? description : undefined}>{description}</p>
|
|
68
68
|
) : null}
|
|
69
69
|
</div>
|
|
70
70
|
|
|
@@ -206,7 +206,7 @@ export function CommandApprovalToast({
|
|
|
206
206
|
>
|
|
207
207
|
<div className="overflow-hidden">
|
|
208
208
|
<div className="flex h-11 w-full items-center gap-2 border-b border-ods-border bg-ods-card px-3 py-2">
|
|
209
|
-
<p className="min-w-0 flex-1 truncate font-['DM_Sans'] text-[14px] font-medium leading-5 text-ods-text-primary">
|
|
209
|
+
<p className="min-w-0 flex-1 truncate font-['DM_Sans'] text-[14px] font-medium leading-5 text-ods-text-primary" title={command}>
|
|
210
210
|
{command}
|
|
211
211
|
</p>
|
|
212
212
|
{toolType ? <ToolIcon toolType={toolType} size={16} /> : null}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { type ReactNode, useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { cn } from '../../utils/cn'
|
|
5
|
+
import { FloatingTooltip } from './floating-tooltip'
|
|
6
|
+
|
|
7
|
+
/** ODS typography variants. Maps to the `.text-h1`…`.text-h6` utilities. */
|
|
8
|
+
export type TruncateTextVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
9
|
+
|
|
10
|
+
/** ODS text tone. Maps to the `text-ods-text-*` colour utilities. */
|
|
11
|
+
export type TruncateTextTone = 'primary' | 'secondary'
|
|
12
|
+
|
|
13
|
+
export interface TruncateTextProps {
|
|
14
|
+
children: string
|
|
15
|
+
/** Tooltip content; defaults to `children`. */
|
|
16
|
+
tooltip?: ReactNode
|
|
17
|
+
/** Extra classes merged after the variant/tone defaults. */
|
|
18
|
+
className?: string
|
|
19
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
20
|
+
/** Max visible lines. `1` uses `truncate` (single-line ellipsis); higher values use `line-clamp-N`. */
|
|
21
|
+
lines?: 1 | 2 | 3 | 4 | 5 | 6
|
|
22
|
+
/** ODS typography token. Default: `'h4'` (body). */
|
|
23
|
+
variant?: TruncateTextVariant
|
|
24
|
+
/** ODS text tone. Default: `'primary'`. */
|
|
25
|
+
tone?: TruncateTextTone
|
|
26
|
+
/** Force the monospace (heading) font family — preserves the variant's size while swapping family. */
|
|
27
|
+
mono?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const VARIANT_CLASS: Record<TruncateTextVariant, string> = {
|
|
31
|
+
h1: 'text-h1',
|
|
32
|
+
h2: 'text-h2',
|
|
33
|
+
h3: 'text-h3',
|
|
34
|
+
h4: 'text-h4',
|
|
35
|
+
h5: 'text-h5',
|
|
36
|
+
h6: 'text-h6',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const TONE_CLASS: Record<TruncateTextTone, string> = {
|
|
40
|
+
primary: 'text-ods-text-primary',
|
|
41
|
+
secondary: 'text-ods-text-secondary',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const LINE_CLAMP_CLASS: Record<2 | 3 | 4 | 5 | 6, string> = {
|
|
45
|
+
2: 'line-clamp-2',
|
|
46
|
+
3: 'line-clamp-3',
|
|
47
|
+
4: 'line-clamp-4',
|
|
48
|
+
5: 'line-clamp-5',
|
|
49
|
+
6: 'line-clamp-6',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Truncated text bound to the ODS typography system. Shows a `FloatingTooltip`
|
|
54
|
+
* with the full value when (and only when) the content overflows.
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <TruncateText>{name}</TruncateText> // h4 / primary
|
|
58
|
+
* <TruncateText variant="h6" tone="secondary">{email}</TruncateText> // caption
|
|
59
|
+
* <TruncateText lines={3}>{description}</TruncateText> // 3-line clamp
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function TruncateText({
|
|
63
|
+
children,
|
|
64
|
+
tooltip,
|
|
65
|
+
className,
|
|
66
|
+
side = 'top',
|
|
67
|
+
lines = 1,
|
|
68
|
+
variant = 'h4',
|
|
69
|
+
tone = 'primary',
|
|
70
|
+
mono = false,
|
|
71
|
+
}: TruncateTextProps) {
|
|
72
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
73
|
+
const [isTruncated, setIsTruncated] = useState(false)
|
|
74
|
+
const isMultiLine = lines > 1
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const el = ref.current
|
|
78
|
+
if (!el) return
|
|
79
|
+
const check = () => {
|
|
80
|
+
const overflows = isMultiLine
|
|
81
|
+
? el.scrollHeight > el.clientHeight + 1
|
|
82
|
+
: el.scrollWidth > el.clientWidth + 1
|
|
83
|
+
setIsTruncated(overflows)
|
|
84
|
+
}
|
|
85
|
+
check()
|
|
86
|
+
const ro = new ResizeObserver(check)
|
|
87
|
+
ro.observe(el)
|
|
88
|
+
return () => ro.disconnect()
|
|
89
|
+
}, [children, isMultiLine])
|
|
90
|
+
|
|
91
|
+
const clampClass = isMultiLine
|
|
92
|
+
? LINE_CLAMP_CLASS[lines as Exclude<typeof lines, 1>]
|
|
93
|
+
: 'truncate block'
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<FloatingTooltip
|
|
97
|
+
content={tooltip ?? children}
|
|
98
|
+
side={side}
|
|
99
|
+
disabled={!isTruncated}
|
|
100
|
+
className="max-w-xs whitespace-pre-line [overflow-wrap:anywhere]"
|
|
101
|
+
>
|
|
102
|
+
<span
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn(
|
|
105
|
+
VARIANT_CLASS[variant],
|
|
106
|
+
TONE_CLASS[tone],
|
|
107
|
+
mono && '[font-family:var(--font-family-heading)]',
|
|
108
|
+
clampClass,
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{children}
|
|
113
|
+
</span>
|
|
114
|
+
</FloatingTooltip>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
@@ -102,13 +102,16 @@ export function UserSummary({
|
|
|
102
102
|
)}
|
|
103
103
|
</div>
|
|
104
104
|
<div className="min-w-0 flex-1">
|
|
105
|
-
<p
|
|
105
|
+
<p
|
|
106
|
+
className="text-h4 text-ods-text-primary truncate"
|
|
107
|
+
title={mspPreview?.name ? `${name} • ${mspPreview.name}` : name}
|
|
108
|
+
>
|
|
106
109
|
{name}
|
|
107
110
|
{mspPreview?.name && (
|
|
108
111
|
<span className="text-ods-text-secondary"> • {mspPreview.name}</span>
|
|
109
112
|
)}
|
|
110
113
|
</p>
|
|
111
|
-
<p className="text-h6 text-ods-text-secondary truncate">
|
|
114
|
+
<p className="text-h6 text-ods-text-secondary truncate" title={subtitle && subtitle.trim().length > 0 ? subtitle : (email && email.trim().length > 0 ? email : '\u00A0')}>
|
|
112
115
|
{subtitle && subtitle.trim().length > 0 ? subtitle : (email && email.trim().length > 0 ? email : '\u00A0')}
|
|
113
116
|
</p>
|
|
114
117
|
</div>
|
|
@@ -154,34 +157,37 @@ export function UserSummary({
|
|
|
154
157
|
<div className="flex-1 grid grid-cols-[1fr_auto] gap-4">
|
|
155
158
|
{/* LEFT : text stack */}
|
|
156
159
|
<div className="min-h-[6rem] flex flex-col justify-center space-y-3 truncate">
|
|
157
|
-
<p className="text-h2 text-ods-text-primary leading-none truncate">
|
|
160
|
+
<p className="text-h2 text-ods-text-primary leading-none truncate" title={name}>
|
|
158
161
|
{name}
|
|
159
162
|
</p>
|
|
160
|
-
<p className="text-h4 text-ods-text-secondary break-all truncate">
|
|
163
|
+
<p className="text-h4 text-ods-text-secondary break-all truncate" title={(subtitle && subtitle.trim().length > 0) ? subtitle : (email && email.trim().length > 0 ? email : '\u00A0')}>
|
|
161
164
|
{(subtitle && subtitle.trim().length > 0) ? subtitle : (email && email.trim().length > 0 ? email : '\u00A0')}
|
|
162
165
|
</p>
|
|
163
|
-
{mspPreview && (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
mspPreview.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
{mspPreview && (() => {
|
|
167
|
+
const mspSegments = [
|
|
168
|
+
mspPreview.name ?? '—',
|
|
169
|
+
typeof mspPreview.seatCount === 'number'
|
|
170
|
+
? `${formatNumber(mspPreview.seatCount)} Seats`
|
|
171
|
+
: null,
|
|
172
|
+
typeof mspPreview.technicianCount === 'number'
|
|
173
|
+
? `${formatNumber(mspPreview.technicianCount)} Technicians`
|
|
174
|
+
: null,
|
|
175
|
+
typeof mspPreview.annualRevenue === 'number'
|
|
176
|
+
? `$${formatNumber(mspPreview.annualRevenue)}`
|
|
177
|
+
: null,
|
|
178
|
+
].filter(Boolean) as string[];
|
|
179
|
+
const mspTitle = mspSegments.join(' • ');
|
|
180
|
+
return (
|
|
181
|
+
<p className="text-h6 text-ods-text-primary truncate" title={mspTitle}>
|
|
182
|
+
{/* Build string with separators */}
|
|
183
|
+
{mspSegments
|
|
184
|
+
.flatMap((txt, idx) => (idx === 0 ? [txt] : [' • ', txt]))
|
|
185
|
+
.map((seg, idx) => (
|
|
186
|
+
<span key={idx} className={seg === ' • ' ? 'text-ods-text-secondary' : ''}>{seg}</span>
|
|
187
|
+
))}
|
|
188
|
+
</p>
|
|
189
|
+
);
|
|
190
|
+
})()}
|
|
185
191
|
</div>
|
|
186
192
|
|
|
187
193
|
{/* RIGHT (desktop) */}
|
|
@@ -79,7 +79,7 @@ export function VendorDisplayButton({ vendor, onClick, variant = 'default', exte
|
|
|
79
79
|
</span>
|
|
80
80
|
</div>
|
|
81
81
|
)}
|
|
82
|
-
<span className="text-h4 text-ods-text-primary truncate min-w-0">
|
|
82
|
+
<span className="text-h4 text-ods-text-primary truncate min-w-0" title={vendor.title}>
|
|
83
83
|
{vendor.title}
|
|
84
84
|
</span>
|
|
85
85
|
</button>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Chevron01DownIcon as ChevronDown,
|
|
4
|
+
Ellipsis02Icon as MoreVertical,
|
|
5
|
+
ExternalLinkIcon as ExternalLink,
|
|
6
|
+
FloppyDiscIcon as Save,
|
|
7
|
+
TrashIcon as Trash2,
|
|
8
|
+
} from '../components/icons-v2-generated'
|
|
3
9
|
import React from 'react'
|
|
4
10
|
import { SplitButton } from '../components/ui/button'
|
|
5
11
|
|