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 ({ 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 - - ({ - 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 } })`, }} diff --git a/packages/react/src/components/value-display/__stories__/ValueDisplay.stories.tsx b/packages/react/src/components/value-display/__stories__/ValueDisplay.stories.tsx index 3d871c9920..cc8b632ee2 100644 --- a/packages/react/src/components/value-display/__stories__/ValueDisplay.stories.tsx +++ b/packages/react/src/components/value-display/__stories__/ValueDisplay.stories.tsx @@ -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", @@ -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, }, }), }, diff --git a/packages/react/src/components/value-display/types/longText.tsx b/packages/react/src/components/value-display/types/longText.tsx index fbcbcbc4e6..e8046e5d29 100644 --- a/packages/react/src/components/value-display/types/longText.tsx +++ b/packages/react/src/components/value-display/types/longText.tsx @@ -13,25 +13,38 @@ const linesValue = (args: LongTextCellValue) => { : undefined } -const tooltipValue = (args: LongTextCellValue) => { - return typeof args === "object" && args !== null && "tooltip" in args - ? args.tooltip - : false +const fullTextValue = (args: LongTextCellValue) => { + return ( + (typeof args === "object" && + args !== null && + "full" in args && + args.full) ?? + false + ) } -export interface LongTextValue extends WithPlaceholder { +export type LongTextValue = WithPlaceholder & { text: string | number | undefined - lines?: number - tooltip?: boolean -} +} & ( + | { + lines?: number + full?: never + } + | { + lines?: never + full: true + } + ) export type LongTextCellValue = string | number | undefined | LongTextValue export const LongTextCell = (args: LongTextCellValue) => { const value = resolveValue(args, "text")?.toString() || "" const shouldShowPlaceholderStyling = isShowingPlaceholder(args, "text") - const lines = linesValue(args) - const noTooltip = !tooltipValue(args) + + const fullText = fullTextValue(args) + + const lines = linesValue(args) || 3 return ( { shouldShowPlaceholderStyling && "text-f1-foreground-secondary" )} lines={lines} - noTooltip={noTooltip} + disabled={fullText} > {value} diff --git a/packages/react/src/experimental/OneDataCollection/__stories__/actions/actions.mdx b/packages/react/src/experimental/OneDataCollection/__stories__/actions/actions.mdx index d678359b56..db20776251 100644 --- a/packages/react/src/experimental/OneDataCollection/__stories__/actions/actions.mdx +++ b/packages/react/src/experimental/OneDataCollection/__stories__/actions/actions.mdx @@ -17,6 +17,18 @@ import * as IndexStories from "../index.stories" ## Introduction +Data Collection provides a hierarchy of actions for data operations: + +- **Primary action**: The main action, visually emphasized, designed for the + most common operation (such as “adding a new resource”). +- **Secondary action**: Complementary actions with less visual prominence. If + there are multiple secondary actions, they should be grouped within the "More + options" (ellipsis) menu, to avoid adding a second button. The goal is to + minimize the toolbar space as much as possible to allow room for other + elements and to avoid overwhelming the user. +- **"More options" button**: A dropdown menu containing additional actions, + optimizing space and maintaining a clean interface. + Data collection allows you to define actions in different levels: ### Collection actions @@ -138,7 +150,11 @@ const dataSource = useDataCollectionSource({ ## Item actions -It's also possible to define actions at item level. +The items provide a streamlined interaction approach: + +- Primary interaction is navigational - clicking a row opens the associated + resource, side panel or modal. +- Do not support individual cell-level actions The actions are defined in the `itemActions` prop of the `useDataCollectionSource` hook via a function that get the item and returns an diff --git a/packages/react/src/experimental/OneDataCollection/__stories__/cell-types.mdx b/packages/react/src/experimental/OneDataCollection/__stories__/cell-types.mdx deleted file mode 100644 index 202cf49180..0000000000 --- a/packages/react/src/experimental/OneDataCollection/__stories__/cell-types.mdx +++ /dev/null @@ -1,29 +0,0 @@ -import { - Canvas, - Meta, - Controls, - Unstyled, - Story, -} from "@storybook/addon-docs/blocks" -import { DoDonts } from "@/lib/storybook-utils/do-donts" -import LinkTo from "@storybook/addon-links/react" - - - -# Cell Types - -## Introduction - -In order to provide an homogeneus experience to the customers and abstract the -render logic to developer using the `ValueDisplay` components, - -ValueDisplay provices a semantic way to define the value's display of a -property. - -The way a cell/property must be rendered is defined in the `render` function of -the datacollection, the type is the cell type to render and the value depends on -that type - -{/* prettier-ignore */} -> Check ValueDisplay -> for details. diff --git a/packages/react/src/experimental/OneDataCollection/__stories__/filters.mdx b/packages/react/src/experimental/OneDataCollection/__stories__/filters.mdx index fa4a761c94..1662bb9e94 100644 --- a/packages/react/src/experimental/OneDataCollection/__stories__/filters.mdx +++ b/packages/react/src/experimental/OneDataCollection/__stories__/filters.mdx @@ -7,10 +7,40 @@ import LinkTo from "@storybook/addon-links/react" ## Introduction -Filters let users narrow down the results in the dataset by one or more -criteria. +Filtering allows users to define custom filtering criteria by selecting options +from various categories through the dropdown panel. -## Presets +### Filtering Panel + +It is activated by clicking on the funnel icon. Once activated, a panel is +displayed with: + +- Filter categories (team, workplace, job title, etc.) +- List of options within each category +- A search field to quickly find specific options +- Selection and action buttons. +- Calendar + - Month and year selector with navigation arrows for browsing different time + periods + - Weekly view with day headers (Mo, Tu, We, Th, Fr, Sa, Su) + - Selectable days for choosing specific dates + - Date range selector with "from" and "to" fields + - Option to either select dates directly on the calendar or enter them + manually + +### Chips + +When a filter is applied, the system generates "chips" that: + +- Accumulate from left to right at the bottom of the presets and filtering + section +- Show the user exactly which filters are active +- Include the category name and the selected value +- Include a close icon (X) that allows the user to remove that specific filter + with a single click +- Remain active until the user explicitly removes them. + +### Presets Presets are predefined filter combinations. For example, if you have a department filter, you can create a preset to show only the Engineering team @@ -73,3 +103,20 @@ argument, independently if the filter was set manually or by a preset. {/* prettier-ignore */} Check the component FiltersPicker documentation for more details about the filters types. + +### Search + +The search bar is a tool that facilitates locating data within the table. Its +main function is to allow users to find specific information quickly and +efficiently. + +- **Collapsible and expandable**: The search field is hidden when not in use and + expands when activated. +- **Real-time search**: As the user types, the results are dynamically updated. +- **Row filtering**: Only rows that match the search criteria are displayed, + hiding those that are not relevant. + + diff --git a/packages/react/src/experimental/OneDataCollection/__stories__/index.mdx b/packages/react/src/experimental/OneDataCollection/__stories__/index.mdx index 46de693582..2d80a966d6 100644 --- a/packages/react/src/experimental/OneDataCollection/__stories__/index.mdx +++ b/packages/react/src/experimental/OneDataCollection/__stories__/index.mdx @@ -1,9 +1,26 @@ import { Canvas, Meta, Controls, Unstyled } from "@storybook/addon-docs/blocks" +import { FeatureGrid } from "~/docs/components/FeatureGrid" +import { + Table, + Rocket, + EyeInvisible, + Cross, + Filter, + Desktop, + CheckCircle, + Save, + Masonry, + Numbers, + Receipt, + Sort, + BookOpen, +} from "@/icons/app" import { DoDonts } from "@/lib/storybook-utils/do-donts" import * as DataCollectionStories from "./index.stories" import * as ActionDataCollectionStories from "./actions/collection-actions.stories" import * as DataCollectionTableViewStories from "./visualizations/table/table.stories" +import { Spinner } from "@/experimental" @@ -29,21 +46,107 @@ primarily through table, card, and list formats. - **Scale with data:** Maintain performance and usability with both small datasets and large volumes of information. - - -### View modes - -Data Collection offers three main data visualization modes, each optimized for -different contexts and needs: - -1. **Table View:** The default view that organizes data into rows and columns. - Ideal for visualizing and comparing multiple data attributes simultaneously. -2. **Cards View:** Provides a more visual and content-oriented layout with - greater emphasis on visuals. Optimized for datasets where resources need to - stand out and visual elements are important. -3. **List View:** Offers a simplified visualization, displaying data in a - vertical list format. Useful when prioritizing readability in simpler - datasets. +# Features + + + +