Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 47 additions & 8 deletions src/components/Hint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';

<BBBHint label="This is a simple hint." />
```

### 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);

<BBBHint
open={open}
onRequestClose={() => 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';

<BBBHint hideCloseButton label="This hint cannot be manually dismissed." />
```

## 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<HTMLDivElement>` | | 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<HTMLDivElement>` | | Any other props will be passed down to the underlying container div. |
59 changes: 57 additions & 2 deletions src/components/Hint/component.stories.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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,
Expand All @@ -32,9 +42,54 @@ const meta = {
export default meta;
type Story = StoryObj<typeof meta>;

/**
* 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<React.ComponentProps<typeof BBBHint>> = (args) => {
const [open, setOpen] = useState(true);

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'flex-start' }}>
<BBButton label="Reopen hint" onClick={() => setOpen(true)} />
<BBBHint
{...args}
open={open}
onRequestClose={() => setOpen(false)}
/>
</div>
);
};

/** 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) => <ControlledHintStory {...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,
},
};
22 changes: 18 additions & 4 deletions src/components/Hint/component.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 = <MdInfo fontSize="1rem" />,
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 = <Styled.Label>{label}{children}</Styled.Label>;
return (
<Styled.Container
Expand All @@ -35,11 +49,11 @@ function Hint({
{title && <Styled.Title>{title}</Styled.Title>}
{!title && renderedLabel}
</Styled.IconTextWrapper>
{title && (
{!hideCloseButton && (
<Styled.CloseButton
type="button"
aria-label="Close"
onClick={onRequestClose}
onClick={handleClose}
>
<MdClose fontSize="1rem" />
</Styled.CloseButton>
Expand Down
10 changes: 8 additions & 2 deletions src/components/Hint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ export interface HintProps extends React.HTMLAttributes<HTMLDivElement> {
/** 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 <MdInfo /> */
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;
}
Loading