diff --git a/.tool-versions b/.tool-versions
index 93a3738b3b..877b26309a 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1 +1 @@
-nodejs 22.5.1
+nodejs 22.5.1 v22.5.1
diff --git a/packages/react/docs/components/FeatureCard.tsx b/packages/react/docs/components/FeatureCard.tsx
index 0c62f6eaae..171327ef73 100644
--- a/packages/react/docs/components/FeatureCard.tsx
+++ b/packages/react/docs/components/FeatureCard.tsx
@@ -1,7 +1,7 @@
import { useState } from "react"
import { F0Icon, IconType } from "../../src/components/F0Icon"
-interface FeatureCardProps {
+export interface FeatureCardProps {
icon: IconType
title: string
description: string
diff --git a/packages/react/docs/components/FeatureGrid.tsx b/packages/react/docs/components/FeatureGrid.tsx
new file mode 100644
index 0000000000..01237016e0
--- /dev/null
+++ b/packages/react/docs/components/FeatureGrid.tsx
@@ -0,0 +1,15 @@
+import { FeatureCard, FeatureCardProps } from "./FeatureCard"
+
+export type FeatureGridProps = {
+ features: FeatureCardProps[]
+}
+
+export const FeatureGrid = ({ features }: FeatureGridProps) => {
+ return (
+
+ {features.map((feature) => (
+
+ ))}
+
+ )
+}
diff --git a/packages/react/src/components/OneEllipsis/OneEllipsis.tsx b/packages/react/src/components/OneEllipsis/OneEllipsis.tsx
index de5607da36..b5a5458fd0 100644
--- a/packages/react/src/components/OneEllipsis/OneEllipsis.tsx
+++ b/packages/react/src/components/OneEllipsis/OneEllipsis.tsx
@@ -5,7 +5,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/ui/tooltip"
-import { forwardRef, useEffect, useMemo, useRef, useState } from "react"
+import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react"
const checkForEllipsis = (element: HTMLElement | null, lines: number) => {
if (!element) return false
@@ -18,12 +18,17 @@ const checkForEllipsis = (element: HTMLElement | null, lines: number) => {
return element.scrollWidth > element.clientWidth
}
+export const tags = ["h1", "h2", "h3", "h4", "h5", "h6", "p", "span"] as const
+export type Tag = (typeof tags)[number]
+
type EllipsisWrapperProps = {
children: string
className?: string
lines: number
noTooltip?: boolean
onHasEllipsisChange: (hasEllipsis: boolean) => void
+ disabled?: boolean
+ tag: Tag
}
/**
@@ -34,13 +39,21 @@ type EllipsisWrapperProps = {
* @param {React.HTMLAttributes} props - The props to apply to the text.
* @returns {React.ReactElement} The rendered text.
*/
-const EllipsisWrapper = forwardRef(
+const EllipsisWrapper = forwardRef(
(
- { children, className, lines, onHasEllipsisChange, noTooltip, ...props },
+ {
+ children,
+ className,
+ lines,
+ onHasEllipsisChange,
+ noTooltip,
+ disabled,
+ ...props
+ },
ref
) => {
useEffect(() => {
- if (!ref || typeof ref !== "object") return
+ if (!ref || typeof ref !== "object" || disabled) return
const element = ref.current
if (!element) return
@@ -58,38 +71,72 @@ const EllipsisWrapper = forwardRef(
return () => {
resizeObserver.disconnect()
}
- }, [ref, onHasEllipsisChange, lines])
-
- return (
- 1
- ? `not-supports-[(-webkit-line-clamp:${lines})]:whitespace-nowrap display-[-webkit-box] whitespace-normal line-clamp-${lines}`
- : "block whitespace-nowrap",
+ }, [ref, onHasEllipsisChange, lines, disabled])
+
+ return React.createElement(
+ props.tag,
+ {
+ ref,
+ className: cn(
+ noTooltip ? "pointer-events-none" : "pointer-events-auto",
+ "min-w-0 max-w-full overflow-hidden",
+ !disabled && [
+ lines === 1 ? "text-ellipsis" : "",
+ lines > 1
+ ? `not-supports-[(-webkit-line-clamp:${lines})]:whitespace-nowrap line-clamp-1 whitespace-normal`
+ : "block whitespace-nowrap",
+ ],
className
- )}
- {...props}
- >
- {children}
-
+ ),
+ style: {
+ WebkitLineClamp: lines > 1 ? lines : undefined,
+ lineClamp: lines > 1 ? lines : undefined,
+ },
+ ...props,
+ },
+ children
)
}
)
EllipsisWrapper.displayName = "EllipsisWrapper"
type OneEllipsisProps = {
+ /**
+ * The className to apply to the text.
+ */
className?: string
+ /**
+ * The number of lines to display.
+ */
lines?: number
+ /**
+ * Whether the ellipsis is disabled.
+ */
+ disabled?: boolean
+ /**
+ * The children to display. (only string is supported)
+ */
children: string
+ /**
+ * Whether the tooltip is disabled.
+ */
noTooltip?: boolean
- tag?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span"
+ /**
+ * The tag to use for the text.
+ */
+ tag?: Tag
}
const OneEllipsis = forwardRef(
(
- { className, lines = 1, children, noTooltip = false, ...props },
+ {
+ className,
+ lines = 1,
+ children,
+ noTooltip = false,
+ disabled = false,
+ ...props
+ },
forwardedRef
) => {
const [hasEllipsis, setHasEllipsis] = useState(false)
@@ -104,7 +151,9 @@ const OneEllipsis = forwardRef(
className={className}
lines={lines}
onHasEllipsisChange={setHasEllipsis}
+ disabled={disabled}
{...props}
+ tag={props.tag ?? "span"}
data-testid="one-ellipsis"
noTooltip={noTooltip}
>
@@ -112,7 +161,7 @@ const OneEllipsis = forwardRef(
)
// eslint-disable-next-line react-hooks/exhaustive-deps -- We dont want to track props as dependencies
- }, [className, lines, children, ref])
+ }, [className, lines, ref, children, disabled])
return hasEllipsis && !noTooltip ? (
diff --git a/packages/react/src/components/OneEllipsis/__stories__/OneEllipsis.stories.tsx b/packages/react/src/components/OneEllipsis/__stories__/OneEllipsis.stories.tsx
index 59241e994b..bc011251c1 100644
--- a/packages/react/src/components/OneEllipsis/__stories__/OneEllipsis.stories.tsx
+++ b/packages/react/src/components/OneEllipsis/__stories__/OneEllipsis.stories.tsx
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import { useId, useState } from "react"
-import { OneEllipsis } from "../OneEllipsis"
+import { OneEllipsis, Tag, tags } from "../OneEllipsis"
const meta = {
title: "Ellipsis",
@@ -18,6 +18,14 @@ const meta = {
},
},
},
+ argTypes: {
+ tag: {
+ control: {
+ type: "select",
+ options: tags,
+ },
+ },
+ },
} satisfies Meta
export default meta
@@ -26,9 +34,17 @@ type Story = StoryObj
const WrapperStory = ({
lines = 1,
text,
+ tag = "span",
+ className = "",
+ disabled = false,
+ noTooltip = false,
}: {
lines?: number
text: string
+ tag?: Tag
+ className?: string
+ disabled?: boolean
+ noTooltip?: boolean
}) => {
const [width, setWidth] = useState<"narrow" | "medium" | "wide">("medium")
@@ -85,12 +101,29 @@ const WrapperStory = ({
- {text}
+
+ {text}
+
)
}
+export const Default: Story = {
+ args: {
+ lines: 1,
+ children:
+ "This is a long text that will be truncated with an ellipsis if it doesn't fit in the container width. Hover over it to see the full text in a tooltip.",
+ },
+ render: (args) => ,
+}
+
export const SingleLine: Story = {
args: {
lines: 1,
diff --git a/packages/react/src/components/value-display/__stories__/ValueDisplay.mdx b/packages/react/src/components/value-display/__stories__/ValueDisplay.mdx
index 5add60b4bf..80c83a86a4 100644
--- a/packages/react/src/components/value-display/__stories__/ValueDisplay.mdx
+++ b/packages/react/src/components/value-display/__stories__/ValueDisplay.mdx
@@ -97,9 +97,13 @@ render: (item) => ({
### longText
-Renders long text with ellipsis truncation and tooltip support. Uses the
+Renders long text with ellipsis truncation with tooltip support. Uses the
OneEllipsis component for better cross-browser support and automatic tooltip
-functionality.
+functionality. By default, the text will be truncated after 3 lines and show an
+ellipsis. When you hover over the text, a tooltip will display the full content.
+
+You can disable the truncation and show the full text by setting the `full`
+property to `true`.
#### Value type
@@ -108,10 +112,17 @@ type value = string | number | undefined
// or
type value = {
text: string | number | undefined
- lines?: number
- tooltip?: boolean
placeholder?: string
-}
+} & (
+ | {
+ lines?: number
+ full?: never
+ }
+ | {
+ lines?: never
+ full: true
+ }
+ )
```
#### LongTextType
@@ -127,7 +138,7 @@ render: (item) => ({
}}
/>
-#### LongTextWithLines
+#### LongText With custom lines number