Skip to content
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nodejs 22.5.1
nodejs 22.5.1 v22.5.1
2 changes: 1 addition & 1 deletion packages/react/docs/components/FeatureCard.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down
15 changes: 15 additions & 0 deletions packages/react/docs/components/FeatureGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FeatureCard, FeatureCardProps } from "./FeatureCard"

export type FeatureGridProps = {
features: FeatureCardProps[]
}

export const FeatureGrid = ({ features }: FeatureGridProps) => {
return (
<div className="mt-6 grid grid-cols-1 gap-6 pb-8 md:grid-cols-3">
{features.map((feature) => (
<FeatureCard key={feature.title} {...feature} />
))}
</div>
)
}
93 changes: 71 additions & 22 deletions packages/react/src/components/OneEllipsis/OneEllipsis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

/**
Expand All @@ -34,13 +39,21 @@ type EllipsisWrapperProps = {
* @param {React.HTMLAttributes<HTMLSpanElement>} props - The props to apply to the text.
* @returns {React.ReactElement} The rendered text.
*/
const EllipsisWrapper = forwardRef<HTMLSpanElement, EllipsisWrapperProps>(
const EllipsisWrapper = forwardRef<HTMLElement, EllipsisWrapperProps>(
(
{ 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
Expand All @@ -58,38 +71,72 @@ const EllipsisWrapper = forwardRef<HTMLSpanElement, EllipsisWrapperProps>(
return () => {
resizeObserver.disconnect()
}
}, [ref, onHasEllipsisChange, lines])

return (
<span
ref={ref}
className={cn(
`${noTooltip ? "pointer-events-none" : "pointer-events-auto"} min-w-0 max-w-full overflow-hidden text-ellipsis`,
lines > 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}
</span>
),
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<HTMLDivElement, OneEllipsisProps>(
(
{ className, lines = 1, children, noTooltip = false, ...props },
{
className,
lines = 1,
children,
noTooltip = false,
disabled = false,
...props
},
forwardedRef
) => {
const [hasEllipsis, setHasEllipsis] = useState(false)
Expand All @@ -104,15 +151,17 @@ const OneEllipsis = forwardRef<HTMLDivElement, OneEllipsisProps>(
className={className}
lines={lines}
onHasEllipsisChange={setHasEllipsis}
disabled={disabled}
{...props}
tag={props.tag ?? "span"}
data-testid="one-ellipsis"
noTooltip={noTooltip}
>
{children}
</EllipsisWrapper>
)
// 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 ? (
<TooltipProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -18,6 +18,14 @@ const meta = {
},
},
},
argTypes: {
tag: {
control: {
type: "select",
options: tags,
},
},
},
} satisfies Meta<typeof OneEllipsis>

export default meta
Expand All @@ -26,9 +34,17 @@ type Story = StoryObj<typeof meta>
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")

Expand Down Expand Up @@ -85,12 +101,29 @@ const WrapperStory = ({
</div>

<div className={`border border-f1-border p-4 ${widthClasses[width]}`}>
<OneEllipsis lines={lines}>{text}</OneEllipsis>
<OneEllipsis
lines={lines}
tag={tag}
className={className}
disabled={disabled}
noTooltip={noTooltip}
>
{text}
</OneEllipsis>
</div>
</div>
)
}

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) => <WrapperStory {...args} text={args.children} />,
}

export const SingleLine: Story = {
args: {
lines: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -127,7 +138,7 @@ render: (item) => ({
}}
/>

#### LongTextWithLines
#### LongText With custom lines number

<Canvas
of={ValueDisplayStories.LongTextWithLines}
Expand All @@ -137,24 +148,7 @@ render: (item) => ({
type: 'longText',
value: {
text: 'This is a very long description that demonstrates the longText cell type with a specific number of lines. The text will be truncated after 2 lines and show an ellipsis. When you hover over the text, a tooltip will display the full content. This is useful for maintaining consistent row heights in tables while still showing more content than a single line would allow.',
lines: 2
}
})`,
}}
/>

#### LongTextWithLinesAndTooltip

<Canvas
of={ValueDisplayStories.LongTextWithLinesAndTooltip}
source={{
code: `
render: (item) => ({
type: 'longText',
value: {
text: 'This is a very long description that demonstrates the longText cell type with a specific number of lines. The text will be truncated after 2 lines and show an ellipsis. When you hover over the text, a tooltip will display the full content. This is useful for maintaining consistent row heights in tables while still showing more content than a single line would allow.',
lines: 2,
tooltip: true
lines: 5
}
})`,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function Cell({
return renderProperty(item, property, "table")
}
const meta = {
title: "Value Display",
title: "Data Collection/Cell Types",
component: Cell,
parameters: {
layout: "padded",
Expand Down Expand Up @@ -165,33 +165,16 @@ export const LongTextType: Story = {
},
}

export const LongTextWithLines: Story = {
export const LongTextFullText: Story = {
args: {
item: mockItem,
property: {
label: "Description (2 lines)",
label: "Description (full text)",
render: () => ({
type: "longText",
value: {
text: "This is a very long description that demonstrates the longText cell type with a specific number of lines. The text will be truncated after 2 lines and show an ellipsis. When you hover over the text, a tooltip will display the full content. This is useful for maintaining consistent row heights in tables while still showing more content than a single line would allow.",
lines: 2,
},
}),
},
},
}

export const LongTextWithLinesAndTooltip: Story = {
args: {
item: mockItem,
property: {
label: "Description (2 lines)",
render: () => ({
type: "longText",
value: {
text: "This is a very long description that demonstrates the longText cell type with a specific number of lines. The text will be truncated after 2 lines and show an ellipsis. When you hover over the text, a tooltip will display the full content. This is useful for maintaining consistent row heights in tables while still showing more content than a single line would allow.",
lines: 2,
tooltip: true,
full: true,
},
}),
},
Expand Down
Loading
Loading