diff --git a/src/components/Hint/README.md b/src/components/Hint/README.md index 33a25ca..c1b1bf5 100644 --- a/src/components/Hint/README.md +++ b/src/components/Hint/README.md @@ -26,13 +26,52 @@ import { BBBHint } from 'bbb-ui-components-react'; /> ``` +### Uncontrolled Hint (default) + +Without an `open` prop, the hint manages its own visibility and closes itself when the close button is clicked — no external state required. + +```jsx +import { BBBHint } from 'bbb-ui-components-react'; + + +``` + +### Controlled Hint + +Pass `open` to drive visibility externally; the hint calls `onRequestClose` instead of hiding itself, leaving the parent in charge of updating `open`. + +```jsx +import { useState } from 'react'; +import { BBBHint } from 'bbb-ui-components-react'; + +const [open, setOpen] = useState(true); + + setOpen(false)} + label="This hint's visibility is controlled externally." +/> +``` + +### Hint Without a Close Button + +Pass `hideCloseButton` for hints that shouldn't be manually dismissed — e.g. ones dismissed by interacting with another UI element, or tooltip-style hints with no explicit dismiss action. + +```jsx +import { BBBHint } from 'bbb-ui-components-react'; + + +``` + ## Props -| Property | Type | Default | Description | -| ---------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------ | -| `label` | `string` | | The main text content of the hint. | -| `title` | `string` | | An optional title for the hint. If provided, a close button will be displayed. | -| `icon` | `React.ReactNode` | | An optional icon to be displayed next to the title or label. | -| `onRequestClose` | `() => void` | | A callback function to be called when the close button is clicked. | -| `children` | `React.ReactNode` | | Optional additional content to be displayed below the label. | -| `...props` | `HTMLAttributes` | | Any other props will be passed down to the underlying container div. | +| Property | Type | Default | Description | +| ----------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------ | +| `label` | `string` | | The main text content of the hint. | +| `title` | `string` | | An optional title shown in the header; when set, `label` renders as a separate line below instead of inline. | +| `icon` | `React.ReactNode` | | An optional icon to be displayed next to the title or label. | +| `open` | `boolean` | | Whether the hint is visible. Omit to let the hint manage its own visibility, closing itself when the close button is clicked; pass a boolean to control visibility externally. | +| `onRequestClose` | `() => void` | | A callback function to be called when the close button is clicked, in both controlled and uncontrolled mode. | +| `hideCloseButton` | `boolean` | `false` | Hides the close (X) button, for hints that shouldn't be manually dismissed. | +| `children` | `React.ReactNode` | | Optional additional content to be displayed below the label. | +| `...props` | `HTMLAttributes` | | Any other props will be passed down to the underlying container div. | diff --git a/src/components/Hint/component.stories.tsx b/src/components/Hint/component.stories.tsx index 65a8a14..a8790e9 100644 --- a/src/components/Hint/component.stories.tsx +++ b/src/components/Hint/component.stories.tsx @@ -1,5 +1,7 @@ +import React, { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import BBBHint from './component'; +import { BBButton } from '../Button'; const meta = { title: 'BBBHint', @@ -12,15 +14,23 @@ const meta = { }, title: { control: 'text', - description: 'Optional title; if provided, shows a close button.', + description: 'Optional title shown in the header; when set, label renders as a separate line below instead of inline.', }, icon: { control: false, description: 'Optional icon node displayed next to the title or label.', }, + open: { + control: 'boolean', + description: 'Whether the hint is visible. Omit to let the hint manage its own visibility, closing itself when the close button is clicked; pass a boolean to control visibility externally.', + }, onRequestClose: { control: false, - description: 'Callback fired when the close button is clicked.', + description: 'Callback fired when the close button is clicked, in both controlled and uncontrolled mode.', + }, + hideCloseButton: { + control: 'boolean', + description: "Hides the close (X) button, for hints that shouldn't be manually dismissed.", }, children: { control: false, @@ -32,9 +42,54 @@ const meta = { export default meta; type Story = StoryObj; +/** + * Wrapper component so hooks can be used inside Storybook's render function, + * demonstrating the `open` prop driving visibility from outside the hint. + */ +const ControlledHintStory: React.FC> = (args) => { + const [open, setOpen] = useState(true); + + return ( +
+ setOpen(true)} /> + setOpen(false)} + /> +
+ ); +}; + /** Basic hint rendering with only a label and the default info icon. */ export const Default: Story = { args: { label: 'Helpful hint', }, }; + +/** Uncontrolled hint (no `open` prop): it manages its own visibility and closes itself when dismissed. */ +export const Uncontrolled: Story = { + args: { + title: 'Uncontrolled', + label: 'This hint manages its own visibility and closes itself when the close button is clicked.', + }, +}; + +/** Controlled hint: visibility is driven by the `open` prop, so the parent decides when it reappears. */ +export const Controlled: Story = { + args: { + title: 'Controlled', + label: "This hint's visibility is controlled externally via the open prop.", + }, + render: (args) => , +}; + +/** Hint with `hideCloseButton`: no close (X) button, for hints that shouldn't be manually dismissed. */ +export const WithoutCloseButton: Story = { + args: { + title: 'No close button', + label: 'This hint cannot be manually dismissed.', + hideCloseButton: true, + }, +}; diff --git a/src/components/Hint/component.tsx b/src/components/Hint/component.tsx index 50bb01b..ee61d3a 100644 --- a/src/components/Hint/component.tsx +++ b/src/components/Hint/component.tsx @@ -1,4 +1,4 @@ -import React, { JSX } from 'react'; +import React, { JSX, useState } from 'react'; import * as Styled from './styles'; import { HintProps } from './types'; import { MdClose, MdInfo } from 'react-icons/md'; @@ -9,15 +9,29 @@ import { MdClose, MdInfo } from 'react-icons/md'; * This component provides a small contextual hint used to surface tips, short help text or dismissible messages. * It can be displayed with a title, an icon, and a close button. * + * Visibility is uncontrolled by default (the hint closes itself when its close button is clicked); pass `open` to control it externally instead. */ function Hint({ title, label, icon = , + open, onRequestClose, + hideCloseButton = false, children, ...rest -}: HintProps): JSX.Element { +}: HintProps): JSX.Element | null { + const isControlled = open !== undefined; + const [internalOpen, setInternalOpen] = useState(true); + const isOpen = isControlled ? open : internalOpen; + + const handleClose = (): void => { + if (!isControlled) setInternalOpen(false); + onRequestClose?.(); + }; + + if (!isOpen) return null; + const renderedLabel = {label}{children}; return ( {title}} {!title && renderedLabel} - {title && ( + {!hideCloseButton && ( diff --git a/src/components/Hint/types.ts b/src/components/Hint/types.ts index eddf53e..f2412d3 100644 --- a/src/components/Hint/types.ts +++ b/src/components/Hint/types.ts @@ -2,15 +2,21 @@ export interface HintProps extends React.HTMLAttributes { /** Main text content of the hint. */ label: string; - /** Optional title; if provided, shows a close button. */ + /** Optional title shown in the header; when set, `label` renders as a separate line below instead of inline. */ title?: string; /** Optional icon node displayed next to the title or label. @default */ icon?: React.ReactNode; - /** Callback fired when the close button is clicked. */ + /** Whether the hint is visible. Omit to let the hint manage its own visibility, closing itself when the close button is clicked; pass a boolean to control visibility externally. */ + open?: boolean; + + /** Callback fired when the close button is clicked, in both controlled and uncontrolled mode. */ onRequestClose?: () => void; + /** Hides the close (X) button, for hints that shouldn't be manually dismissed. @default false */ + hideCloseButton?: boolean; + /** Optional additional content rendered under the label. */ children?: React.ReactNode; }