@flamingo-stack/openframe-frontend-core 0.0.201 → 0.0.202-snapshot.20260521221224

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 (130) hide show
  1. package/dist/{chunk-OII2IERE.cjs → chunk-25LVV26X.cjs} +4 -4
  2. package/dist/chunk-25LVV26X.cjs.map +1 -0
  3. package/dist/{chunk-UCY537V4.cjs → chunk-3YH2M76N.cjs} +1565 -1146
  4. package/dist/chunk-3YH2M76N.cjs.map +1 -0
  5. package/dist/{chunk-55HF462A.js → chunk-CPXLQ57U.js} +6 -7
  6. package/dist/chunk-CPXLQ57U.js.map +1 -0
  7. package/dist/{chunk-CSW5GYBU.js → chunk-E6Q6UGDK.js} +4603 -4184
  8. package/dist/chunk-E6Q6UGDK.js.map +1 -0
  9. package/dist/{chunk-3B43AHYE.cjs → chunk-RMB5DVED.cjs} +6 -7
  10. package/dist/chunk-RMB5DVED.cjs.map +1 -0
  11. package/dist/{chunk-4ML3NA2L.js → chunk-XGL5FKIK.js} +4 -4
  12. package/dist/chunk-XGL5FKIK.js.map +1 -0
  13. package/dist/components/chat/approval-request-message.d.ts.map +1 -1
  14. package/dist/components/chat/chat-container.d.ts.map +1 -1
  15. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  16. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  17. package/dist/components/chat/chat-ticket-item.d.ts.map +1 -1
  18. package/dist/components/chat/types/message.types.d.ts +34 -0
  19. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  20. package/dist/components/features/index.cjs +16 -4
  21. package/dist/components/features/index.cjs.map +1 -1
  22. package/dist/components/features/index.d.ts +1 -0
  23. package/dist/components/features/index.d.ts.map +1 -1
  24. package/dist/components/features/index.js +17 -5
  25. package/dist/components/features/select-button.d.ts.map +1 -1
  26. package/dist/components/index.cjs +18 -4
  27. package/dist/components/index.cjs.map +1 -1
  28. package/dist/components/index.js +17 -3
  29. package/dist/components/navigation/index.cjs +4 -4
  30. package/dist/components/navigation/index.js +3 -3
  31. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  32. package/dist/components/providers/theme-provider.d.ts +69 -0
  33. package/dist/components/providers/theme-provider.d.ts.map +1 -0
  34. package/dist/components/resizable.d.ts +1 -1
  35. package/dist/components/ui/button/split-button.d.ts.map +1 -1
  36. package/dist/components/ui/data-table/data-table-row.d.ts +16 -4
  37. package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
  38. package/dist/components/ui/file-manager/index.cjs +52 -52
  39. package/dist/components/ui/file-manager/index.cjs.map +1 -1
  40. package/dist/components/ui/file-manager/index.js +3 -3
  41. package/dist/components/ui/file-manager/index.js.map +1 -1
  42. package/dist/components/ui/floating-tooltip.d.ts +3 -1
  43. package/dist/components/ui/floating-tooltip.d.ts.map +1 -1
  44. package/dist/components/ui/index.cjs +6 -4
  45. package/dist/components/ui/index.cjs.map +1 -1
  46. package/dist/components/ui/index.d.ts +1 -0
  47. package/dist/components/ui/index.d.ts.map +1 -1
  48. package/dist/components/ui/index.js +5 -3
  49. package/dist/components/ui/input-trigger.d.ts.map +1 -1
  50. package/dist/components/ui/radio-group.d.ts.map +1 -1
  51. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
  52. package/dist/components/ui/ticket-info-section.d.ts.map +1 -1
  53. package/dist/components/ui/ticket-note-card.d.ts.map +1 -1
  54. package/dist/components/ui/truncate-text.d.ts +33 -0
  55. package/dist/components/ui/truncate-text.d.ts.map +1 -0
  56. package/dist/components/user-summary-stub.d.ts.map +1 -1
  57. package/dist/hooks/index.cjs +2 -2
  58. package/dist/hooks/index.js +1 -1
  59. package/dist/index.cjs +18 -4
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.js +17 -3
  62. package/package.json +2 -1
  63. package/src/components/chat/approval-request-message.tsx +106 -92
  64. package/src/components/chat/chat-container.tsx +10 -6
  65. package/src/components/chat/chat-message-enhanced.tsx +51 -9
  66. package/src/components/chat/chat-message-list.tsx +27 -19
  67. package/src/components/chat/chat-ticket-item.tsx +2 -3
  68. package/src/components/chat/types/message.types.ts +35 -0
  69. package/src/components/features/board/ticket-card.tsx +2 -2
  70. package/src/components/features/filters-dropdown.tsx +1 -1
  71. package/src/components/features/index.ts +15 -0
  72. package/src/components/features/notifications/notification-tile.tsx +2 -2
  73. package/src/components/features/policy-configuration-panel.tsx +1 -1
  74. package/src/components/features/push-button-selector.tsx +1 -1
  75. package/src/components/features/select-button.tsx +2 -3
  76. package/src/components/features/video-bites-display.tsx +1 -1
  77. package/src/components/features/waitlist-form.tsx +1 -1
  78. package/src/components/filter-chip.tsx +1 -1
  79. package/src/components/layout/title-block.tsx +2 -2
  80. package/src/components/navigation/header-organization-filter.tsx +1 -1
  81. package/src/components/navigation/navigation-sidebar.tsx +107 -54
  82. package/src/components/platform/ScriptInfoSection.tsx +1 -1
  83. package/src/components/providers/theme-provider.tsx +130 -0
  84. package/src/components/shared/onboarding/onboarding-step-card.tsx +2 -2
  85. package/src/components/shared/product-release/product-release-card.tsx +6 -6
  86. package/src/components/shared/product-release/release-detail-page.tsx +1 -1
  87. package/src/components/ui/assignee-dropdown.tsx +3 -3
  88. package/src/components/ui/autocomplete.tsx +2 -2
  89. package/src/components/ui/button/split-button.tsx +3 -5
  90. package/src/components/ui/checkbox-block.tsx +1 -1
  91. package/src/components/ui/data-table/data-table-row.tsx +82 -48
  92. package/src/components/ui/device-card-compact.tsx +2 -2
  93. package/src/components/ui/device-card.tsx +2 -2
  94. package/src/components/ui/entity-image.tsx +1 -1
  95. package/src/components/ui/field-wrapper.tsx +1 -1
  96. package/src/components/ui/file-manager/file-manager-table-row.tsx +2 -2
  97. package/src/components/ui/file-upload.tsx +2 -2
  98. package/src/components/ui/filter-list.tsx +1 -1
  99. package/src/components/ui/floating-tooltip.tsx +9 -5
  100. package/src/components/ui/hidden-tags-popup.tsx +1 -1
  101. package/src/components/ui/index.ts +1 -0
  102. package/src/components/ui/info-card.tsx +2 -2
  103. package/src/components/ui/input-trigger.tsx +1 -2
  104. package/src/components/ui/organization-card.tsx +3 -3
  105. package/src/components/ui/radio-group.tsx +2 -3
  106. package/src/components/ui/search-input.tsx +2 -2
  107. package/src/components/ui/service-card.tsx +3 -3
  108. package/src/components/ui/simple-markdown-renderer.tsx +248 -2
  109. package/src/components/ui/tag.tsx +1 -1
  110. package/src/components/ui/tags-manager.tsx +2 -2
  111. package/src/components/ui/ticket-attachments-list.tsx +1 -1
  112. package/src/components/ui/ticket-info-section.tsx +2 -3
  113. package/src/components/ui/ticket-note-card.tsx +4 -1
  114. package/src/components/ui/toaster.tsx +3 -3
  115. package/src/components/ui/truncate-text.tsx +116 -0
  116. package/src/components/user-summary-stub.tsx +32 -26
  117. package/src/components/vendor-display-button.tsx +1 -1
  118. package/src/stories/SplitButton.stories.tsx +7 -1
  119. package/src/stories/Theme.stories.tsx +350 -0
  120. package/src/styles/README.md +271 -174
  121. package/src/styles/dark_theme.tokens.json +982 -0
  122. package/src/styles/light_theme.tokens.json +982 -0
  123. package/src/styles/ods-colors.css +225 -146
  124. package/src/styles/ods_color_tokens.json +1 -300
  125. package/dist/chunk-3B43AHYE.cjs.map +0 -1
  126. package/dist/chunk-4ML3NA2L.js.map +0 -1
  127. package/dist/chunk-55HF462A.js.map +0 -1
  128. package/dist/chunk-CSW5GYBU.js.map +0 -1
  129. package/dist/chunk-OII2IERE.cjs.map +0 -1
  130. package/dist/chunk-UCY537V4.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import Link from 'next/link'
4
- import React, { memo, useCallback, type ReactNode } from 'react'
4
+ import React, { memo, useCallback, useRef, type ReactNode } from 'react'
5
5
  import { flexRender, type Row } from '@tanstack/react-table'
6
6
  import { cn } from '../../../utils/cn'
7
7
  import { ROW_HEIGHT_DESKTOP } from './data-table-skeleton'
@@ -19,16 +19,28 @@ export interface DataTableRowProps<T> {
19
19
  /**
20
20
  * Click-bubbling protocol: any element inside a cell that should NOT trigger
21
21
  * `onRowClick` / row navigation must carry the `data-no-row-click` attribute.
22
- * The row checks `target.closest('[data-no-row-click]')` before firing
23
- * `onClick(item)`. This is the single primitive that interactive cells
24
- * (action buttons, dropdown menus, checkboxes) must opt into.
22
+ * The row checks `target.closest('[data-no-row-click]')` and short-circuits:
23
+ * in `onClick` mode it skips the consumer's handler; in link mode (when
24
+ * `href` is set) it calls `e.preventDefault()` so `<Link>` does not navigate.
25
+ *
26
+ * Clicks originating from portaled descendants (e.g. `FloatingTooltip`,
27
+ * dropdown menus rendered through `FloatingPortal`) bubble through React's
28
+ * component tree and reach this handler, but their DOM target lives outside
29
+ * the row subtree. The handler ignores any click whose target is not
30
+ * physically contained within the row element — no `stopPropagation`
31
+ * required at the source.
32
+ *
33
+ * In link mode the row IS the `<Link>` — content lives inside it, not under
34
+ * an absolute overlay — so native browser link behaviour works: hover,
35
+ * right-click "Open in new tab", middle-click, `Cmd+click`, focus outlines,
36
+ * `:visited` styles, etc.
25
37
  *
26
38
  * Example column with action buttons:
27
39
  * ```tsx
28
40
  * {
29
41
  * id: 'actions',
30
42
  * cell: ({ row }) => (
31
- * <div data-no-row-click className="flex gap-2 justify-end pointer-events-auto">
43
+ * <div data-no-row-click className="flex gap-2 justify-end">
32
44
  * <Button onClick={() => edit(row.original)}>Edit</Button>
33
45
  * </div>
34
46
  * ),
@@ -49,61 +61,83 @@ function DataTableRowImpl<T>({
49
61
  className,
50
62
  }: DataTableRowProps<T>) {
51
63
  const isLinkMode = Boolean(href) && !onClick
64
+ const containerRef = useRef<HTMLElement | null>(null)
52
65
 
53
66
  const handleClick = useCallback(
54
67
  (e: React.MouseEvent) => {
55
68
  const target = e.target as HTMLElement
56
- if (target.closest('[data-no-row-click]')) return
69
+ // React-bubbled events from portaled descendants (tooltips, dropdowns, etc.)
70
+ // reach this handler even though their DOM target lives outside the row.
71
+ // Suppress them — and in link mode, preventDefault so `<Link>` does not navigate.
72
+ if (!containerRef.current?.contains(target)) {
73
+ if (isLinkMode) e.preventDefault()
74
+ return
75
+ }
76
+ if (target.closest('[data-no-row-click]')) {
77
+ if (isLinkMode) e.preventDefault()
78
+ return
79
+ }
57
80
  onClick?.(row.original)
58
81
  },
59
- [onClick, row.original],
82
+ [onClick, row.original, isLinkMode],
60
83
  )
61
84
 
62
- return (
85
+ const containerClassName = cn(
86
+ 'block rounded-md bg-ods-card border border-ods-border overflow-hidden no-underline text-inherit',
87
+ (onClick || isLinkMode) && 'cursor-pointer hover:bg-ods-bg-active transition-colors',
88
+ className,
89
+ )
90
+
91
+ const cells = (
63
92
  <div
64
93
  className={cn(
65
- 'relative rounded-md bg-ods-card border border-ods-border overflow-hidden',
66
- (onClick || isLinkMode) &&
67
- 'cursor-pointer hover:bg-ods-bg-active transition-colors',
68
- className,
94
+ 'flex items-center gap-[var(--spacing-system-mf)] px-[var(--spacing-system-mf)]',
95
+ compact ? 'py-[var(--spacing-system-xsf)]' : `py-0 ${ROW_HEIGHT_DESKTOP}`,
69
96
  )}
70
- onClick={isLinkMode ? undefined : handleClick}
71
97
  >
72
- {isLinkMode && href && (
73
- <Link
74
- href={href}
75
- prefetch={false}
76
- className="absolute inset-0"
77
- aria-label="View details"
78
- />
79
- )}
80
- <div
81
- className={cn(
82
- 'relative flex items-center gap-[var(--spacing-system-mf)] px-[var(--spacing-system-mf)]',
83
- compact ? 'py-[var(--spacing-system-xsf)]' : `py-0 ${ROW_HEIGHT_DESKTOP}`,
84
- isLinkMode && 'pointer-events-none',
85
- )}
98
+ {row.getVisibleCells().map(cell => {
99
+ const meta = cell.column.columnDef.meta
100
+ return (
101
+ <div
102
+ key={cell.id}
103
+ className={cn(
104
+ 'flex flex-col overflow-hidden',
105
+ alignJustify(meta?.align),
106
+ meta?.width || 'flex-1 min-w-0',
107
+ meta?.cellClassName,
108
+ getHideClasses(meta?.hideAt),
109
+ )}
110
+ >
111
+ <CellContent>
112
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
113
+ </CellContent>
114
+ </div>
115
+ )
116
+ })}
117
+ </div>
118
+ )
119
+
120
+ if (isLinkMode && href) {
121
+ return (
122
+ <Link
123
+ href={href}
124
+ prefetch={false}
125
+ ref={containerRef as React.RefObject<HTMLAnchorElement>}
126
+ className={containerClassName}
127
+ onClick={handleClick}
86
128
  >
87
- {row.getVisibleCells().map(cell => {
88
- const meta = cell.column.columnDef.meta
89
- return (
90
- <div
91
- key={cell.id}
92
- className={cn(
93
- 'flex flex-col overflow-hidden',
94
- alignJustify(meta?.align),
95
- meta?.width || 'flex-1 min-w-0',
96
- meta?.cellClassName,
97
- getHideClasses(meta?.hideAt),
98
- )}
99
- >
100
- <CellContent>
101
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
102
- </CellContent>
103
- </div>
104
- )
105
- })}
106
- </div>
129
+ {cells}
130
+ </Link>
131
+ )
132
+ }
133
+
134
+ return (
135
+ <div
136
+ ref={containerRef as React.RefObject<HTMLDivElement>}
137
+ className={containerClassName}
138
+ onClick={onClick ? handleClick : undefined}
139
+ >
140
+ {cells}
107
141
  </div>
108
142
  )
109
143
  }
@@ -114,7 +148,7 @@ export const DataTableRow = memo(DataTableRowImpl) as typeof DataTableRowImpl
114
148
  function CellContent({ children }: { children: ReactNode }) {
115
149
  if (typeof children === 'string' || typeof children === 'number') {
116
150
  return (
117
- <span className="text-h4 text-ods-text-primary truncate">{children}</span>
151
+ <span className="text-h4 text-ods-text-primary truncate" title={String(children)}>{children}</span>
118
152
  )
119
153
  }
120
154
  return <>{children}</>
@@ -35,12 +35,12 @@ export function DeviceCardCompact({
35
35
  {...props}
36
36
  >
37
37
  {hasName && (
38
- <span className="font-['DM_Sans'] font-medium text-[16px] leading-[20px] text-ods-text-primary truncate">
38
+ <span className="font-['DM_Sans'] font-medium text-[16px] leading-[20px] text-ods-text-primary truncate" title={deviceName}>
39
39
  {deviceName}
40
40
  </span>
41
41
  )}
42
42
  {hasOrg && (
43
- <span className="font-['DM_Sans'] font-medium text-[14px] leading-[18px] text-ods-text-secondary truncate">
43
+ <span className="font-['DM_Sans'] font-medium text-[14px] leading-[18px] text-ods-text-secondary truncate" title={organization}>
44
44
  {organization}
45
45
  </span>
46
46
  )}
@@ -103,12 +103,12 @@ export function DeviceCard({
103
103
  className="shrink-0"
104
104
  />
105
105
  )}
106
- <span className="text-h4 text-ods-text-primary truncate">
106
+ <span className="text-h4 text-ods-text-primary truncate" title={device.name}>
107
107
  {device.name}
108
108
  </span>
109
109
  </div>
110
110
  {device.organization && (
111
- <span className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate">
111
+ <span className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate" title={device.organization}>
112
112
  {device.organization}
113
113
  </span>
114
114
  )}
@@ -48,7 +48,7 @@ export function EntityImage({ src, alt, fallbackText, className }: EntityImagePr
48
48
  alt={alt ?? ''}
49
49
  onError={() => setImageFailed(true)}
50
50
  className={cn(
51
- 'size-[52px] md:size-[60px] shrink-0 rounded-md border border-ods-border object-cover',
51
+ 'size-[52px] md:size-[60px] shrink-0 rounded-md border border-ods-border object-contain',
52
52
  className,
53
53
  )}
54
54
  />
@@ -33,7 +33,7 @@ const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
33
33
  )}
34
34
  {children}
35
35
  {error && (
36
- <p className={cn("absolute bottom-0 left-0 right-0 translate-y-full text-h6 truncate", errorVariantClasses[errorVariant])}>
36
+ <p className={cn("absolute bottom-0 left-0 right-0 translate-y-full text-h6 truncate", errorVariantClasses[errorVariant])} title={error}>
37
37
  {error}
38
38
  </p>
39
39
  )}
@@ -76,11 +76,11 @@ export function FileManagerTableRow({
76
76
  size="md"
77
77
  />
78
78
  <div className="flex flex-col min-w-0">
79
- <span className="text-sm text-ods-text-primary truncate">
79
+ <span className="text-sm text-ods-text-primary truncate" title={file.name}>
80
80
  {file.name}
81
81
  </span>
82
82
  {showPath && file.path && (
83
- <span className="text-xs text-ods-text-secondary truncate">
83
+ <span className="text-xs text-ods-text-secondary truncate" title={file.path}>
84
84
  {file.path}
85
85
  </span>
86
86
  )}
@@ -347,7 +347,7 @@ export function FileUpload({
347
347
  )}
348
348
  >
349
349
  <div className="flex-1 min-w-0">
350
- <p className="text-heading-5 font-medium text-ods-text-primary truncate">
350
+ <p className="text-heading-5 font-medium text-ods-text-primary truncate" title={entry.fileName}>
351
351
  {entry.fileName}
352
352
  </p>
353
353
  <div className="flex items-center gap-2">
@@ -385,7 +385,7 @@ export function FileUpload({
385
385
  className="flex items-center gap-3 p-3 rounded-[6px] bg-ods-card border border-ods-border"
386
386
  >
387
387
  <div className="flex-1 min-w-0">
388
- <p className="text-heading-5 font-medium text-ods-text-primary truncate">
388
+ <p className="text-heading-5 font-medium text-ods-text-primary truncate" title={file.name}>
389
389
  {file.name}
390
390
  </p>
391
391
  <p className="text-heading-6 text-ods-text-secondary">
@@ -56,7 +56,7 @@ export function FilterListItem({
56
56
  )}
57
57
  >
58
58
  <div className="flex min-w-0 flex-1 flex-col">
59
- <p className="truncate text-h4 text-ods-text-primary">
59
+ <p className="truncate text-h4 text-ods-text-primary" title={title}>
60
60
  {title}
61
61
  </p>
62
62
 
@@ -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