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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Below is a list of the components available in this library. Each component has
- [BBBCheckbox](./src/components/Checkbox/README.md)
- [BBBDivider](./src/components/Divider/README.md)
- [BBBHint](./src/components/Hint/README.md)
- [BBBInput](./src/components/Input/README.md)
- [BBBModal](./src/components/Modal//README.md)
- [BBBNavigation](./src/components/Navigation/README.md)
- [BBBSearch](./src/components/Search/README.md)
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
"require": "./dist/components/Hint.js",
"default": "./dist/components/Hint.js"
},
"./Input": {
"types": "./dist/types/components/Input/index.d.ts",
"node": "./dist/components/Input.js",
"import": "./dist/esm/components/Input/index.js",
"require": "./dist/components/Input.js",
"default": "./dist/components/Input.js"
},
"./Modal": {
"types": "./dist/types/components/Modal/index.d.ts",
"node": "./dist/components/Modal.js",
Expand Down Expand Up @@ -172,6 +179,9 @@
"Hint": [
"dist/types/components/Hint/index.d.ts"
],
"Input": [
"dist/types/components/Input/index.d.ts"
],
"Modal": [
"dist/types/components/Modal/index.d.ts"
],
Expand Down
13 changes: 11 additions & 2 deletions src/components/Button/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BBButton

The `BBButton` component is a versatile button that supports multiple layouts and styles. It can be rendered as a standard inline button, a circular icon button, or a stacked button with an icon and a label.
The `BBButton` component is a versatile button that supports multiple layouts and styles. It can be rendered as a standard inline button, a circular icon button, a compact squared icon button, or a stacked button with an icon and a label.

![Demo](assets/example.png)

Expand Down Expand Up @@ -33,6 +33,15 @@ import { MdFavorite } from 'react-icons/md';
<BBButton layout="circle" icon={<MdFavorite />} tooltipLabel="Favorite" onClick={() => (console.log('clicked'))}/>
```

### Squared Button

```jsx
import { BBButton } from 'bbb-ui-components-react';
import { MdSend } from 'react-icons/md';

<BBButton layout="squared" icon={<MdSend />} ariaLabel="Send" onClick={() => (console.log('clicked'))}/>
```

### Stacked Button

```jsx
Expand Down Expand Up @@ -62,7 +71,7 @@ import { MdPlayArrow } from 'react-icons/md';
| `layout` | `keyof typeof LAYOUTS` | `'default'` | The layout of the button. |
| `disabled` | `boolean` | `false` | If `true`, the button will be disabled. |
| `children` | `React.ReactNode` | | The content of the button, typically used for the 'default' layout. |
| `icon` | `React.ReactNode` | | The icon to be displayed. Used for 'circle' and 'stacked' layouts. |
| `icon` | `React.ReactNode` | | The icon to be displayed. Used for 'circle', 'squared' and 'stacked' layouts. |
| `iconStart` | `React.ReactNode` | | The icon to be displayed at the start of the button. Used for 'default' layout.|
| `iconEnd` | `React.ReactNode` | | The icon to be displayed at the end of the button. Used for 'default' layout. |
| `helperIcon` | `React.ReactNode` | | The auxiliary icon to be displayed. Used for 'stacked' layout. |
Expand Down
24 changes: 23 additions & 1 deletion src/components/Button/component.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const meta = {
},
icon: {
control: false,
description: `Main icon for the button. Commonly used for icon-only buttons (layout 'circle') or in 'stacked' layout. When using a label, prefer \`iconStart\` or \`iconEnd\` in the 'default' layout.`,
description: `Main icon for the button. Commonly used for icon-only buttons (layouts 'circle' and 'squared') or in 'stacked' layout. When using a label, prefer \`iconStart\` or \`iconEnd\` in the 'default' layout.`,
table: { defaultValue: { summary: 'null' } },
},
iconStart: {
Expand Down Expand Up @@ -247,6 +247,28 @@ export const Circle: Story = {
),
};

export const Squared: Story = {
name: 'Squared',
args: {
tooltipPlacement: 'top',
variant: 'primary',
layout: 'squared',
icon: <MdFavorite size="1.25rem" />,
},
render: (args) => (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
{COLOR_VALUES.map((color) => (
<BBButton
{...args}
key={color}
color={color}
tooltipLabel={`Squared ${color.charAt(0).toUpperCase() + color.slice(1)}`}
/>
))}
</div>
),
};

export const WithIcon: Story = {
args: {
label: 'Like',
Expand Down
2 changes: 1 addition & 1 deletion src/components/Button/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function Button(props: ButtonProps): JSX.Element {
if (ariaDescribedBy) accessibilityProps['aria-describedby'] = ariaDescribedBy;

const buttonElement = (() => {
if (props.layout === LAYOUTS.CIRCLE) {
if (props.layout === LAYOUTS.CIRCLE || props.layout === LAYOUTS.SQUARED) {
const { icon } = props;
const testId = dataTest || `button-${id || 'default'}`;

Expand Down
1 change: 1 addition & 0 deletions src/components/Button/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const LAYOUTS = {
DEFAULT: 'default',
STACKED: 'stacked',
CIRCLE: 'circle',
SQUARED: 'squared',
} as const;
const LAYOUT_VALUES = Object.values(LAYOUTS);
const DEFAULT_LAYOUT = LAYOUT_VALUES[0];
Expand Down
15 changes: 15 additions & 0 deletions src/components/Button/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from './type';
import {
borderRadiusDefault,
borderRadiusSmall,
spacingSmall,
} from '../../stylesheets/sizing';
import { colorBorderDefault, colorTextDefault } from '../../stylesheets/pallete';
Expand Down Expand Up @@ -129,6 +130,18 @@ const circleLayoutStyles = css`
align-items: center;
`;

// Icon-only, sized by its own (equal, non-text) padding rather than a fixed
// box — stays compact next to a single line of text instead of forcing a
// fixed height like circle/stacked do.
const squaredLayoutStyles = css`
border-radius: ${borderRadiusSmall};
aspect-ratio: 1;
padding: ${spacingSmall};
display: flex;
justify-content: center;
align-items: center;
`;

const defaultLayoutStyles = css`
display: inline-flex;
justify-content: center;
Expand Down Expand Up @@ -156,6 +169,8 @@ export const Button = styled.button<StyledButtonProps>`
return stackedLayoutStyles;
case LAYOUTS.CIRCLE:
return circleLayoutStyles;
case LAYOUTS.SQUARED:
return squaredLayoutStyles;
default:
return defaultLayoutStyles;
}
Expand Down
7 changes: 6 additions & 1 deletion src/components/Button/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@ type CircleLayoutProps = BaseButtonProps & {
icon?: React.ReactNode;
};

export type ButtonProps = DefaultLayoutProps | StackedLayoutProps | CircleLayoutProps;
type SquaredLayoutProps = BaseButtonProps & {
layout: typeof LAYOUTS.SQUARED;
icon?: React.ReactNode;
};

export type ButtonProps = DefaultLayoutProps | StackedLayoutProps | CircleLayoutProps | SquaredLayoutProps;
118 changes: 118 additions & 0 deletions src/components/Input/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# BBBInput

The `BBBInput` component provides a labeled, auto-growing text field with an inline action button. It unifies the recurring "text field + submit button" pattern used across chat, guest messages, breakouts and similar areas, modeled after the core chat text input.

`BBBTextInput` and `BBBTextAreaInput` remain the right choice for standalone fields with no inline action button.

## Usage Example

### Basic usage — text field with a submit icon button
```jsx
import { BBBInput } from 'bbb-ui-components-react';
import { MdSend } from 'react-icons/md';

<BBBInput
label="Message"
placeholder="Type your message..."
buttonIcon={<MdSend />}
buttonAriaLabel="Send message"
onSubmit={(value) => handleSend(value)}
/>
```

### With a text button label instead of an icon
```jsx
<BBBInput
label="Message to guests"
placeholder="Write something..."
buttonLabel="Send"
onSubmit={(value) => sendGuestMessage(value)}
/>
```

### Controlled
```jsx
const [value, setValue] = useState('');

<BBBInput
label="Message to rooms"
value={value}
onChange={setValue}
buttonIcon={<MdSend />}
buttonAriaLabel="Send message"
onSubmit={(submittedValue) => {
submitMessage(submittedValue);
setValue('');
}}
/>
```

### With sent feedback
```jsx
<BBBInput
label="Message"
buttonIcon={<MdSend />}
buttonAriaLabel="Send message"
showSentFeedback
onSubmit={(value) => handleSend(value)}
/>
```

### With extra actions around the submit button
```jsx
import { MdEmojiEmotions } from 'react-icons/md';

<BBBInput
label="Message"
buttonIcon={<MdSend />}
buttonAriaLabel="Send message"
beforeButton={(
<button type="button" aria-label="Insert emoji" onClick={openEmojiPicker}>
<MdEmojiEmotions />
</button>
)}
onSubmit={(value) => handleSend(value)}
/>
```

## Props

| Property | Type | Default | Description |
| ----------------------- | --------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------ |
| `label` | `string` | | Label displayed above the field. |
| `placeholder` | `string` | | Placeholder text displayed when the field is empty. |
| `value` | `string` | | Controlled value. Enables controlled mode when provided together with `onChange`. |
| `onChange` | `(value: string) => void` | | Called on every keystroke. Required in controlled mode. |
| `onSubmit` | `(value: string) => void` | | Called with the trimmed, non-empty value when the field is submitted. **Required.** |
| `buttonIcon` | `React.ReactNode` | | Icon rendered inside the action button. |
| `buttonLabel` | `string` | | Text label rendered by the action button, instead of (or alongside) `buttonIcon`. |
| `buttonAriaLabel` | `string` | | Accessible name for the action button. **Required** when only `buttonIcon` is set (no `buttonLabel`). |
| `buttonVariant` | `'primary' \| 'secondary' \| 'tertiary' \| 'subtle'` | `'primary'` | Visual variant of the action button (same variants as the shared `Button`). |
| `dataTest` | `string` | | `data-test` attribute on the underlying `<textarea>` element. |
| `buttonDataTest` | `string` | | `data-test` attribute on the action button. |
| `beforeButton` | `React.ReactNode` | | Extra content (icon, button, or any other element) rendered to the left of the action button. |
| `afterButton` | `React.ReactNode` | | Extra content (icon, button, or any other element) rendered to the right of the action button. |
| `submitOnEnter` | `boolean` | `true` | When `true`, Enter submits (Shift+Enter inserts a newline). When `false`, only the button submits. |
| `showSentFeedback` | `boolean` | `false` | Shows `sentFeedbackContent` in place of `helperText` for `sentFeedbackDuration` after a successful submit. |
| `sentFeedbackContent` | `React.ReactNode` | `'Message sent'` | Content rendered while the sent feedback is visible — accepts any node, not just plain text. |
| `sentFeedbackDuration` | `number` | `2000` | How long, in milliseconds, the sent feedback stays visible. |
| `disabled` | `boolean` | `false` | Disables the field and the action button. |
| `error` | `boolean` | `false` | Puts the field in an error state. |
| `helperText` | `string` | | Helper text displayed below the field. |
| `minRows` | `number` | `1` | Minimum number of visible rows. |
| `maxRows` | `number` | `3` | Maximum number of rows before the field scrolls internally. |
| `maxLength` | `number` | | Maximum character length, enforced natively by the underlying `<textarea>`. |
| `autoFocus` | `boolean` | `false` | Focuses the field when the component mounts. |
| `inputRef` | `React.Ref<HTMLTextAreaElement>` | | Ref forwarded to the underlying `<textarea>` element. |
| `aria-label` | `string` | | Accessible name for the field, for cases with no visible `label`. |
| `aria-describedby` | `string` | | Extra `aria-describedby` id(s), combined with the helper/feedback text's own id when one is rendered. |

## Behavior Notes

- **Field type**: the text field is a `<textarea>` that auto-grows from `minRows` to `maxRows` (default `3`) as content is typed, then scrolls internally past `maxRows` — same auto-grow behavior as the reference chat input, with a shorter default cap.
- **Uncontrolled mode** (default): the component manages its own value, clearing and refocusing the field after a successful submit.
- **Controlled mode**: when `value` + `onChange` are both provided, the parent owns the value entirely; clearing it after submit (if desired) is the consumer's responsibility, typically inside `onSubmit`.
- **Empty submits are ignored**: submitting a blank or whitespace-only value is a no-op — the field never sends empty messages.
- **Enter to submit**: with `submitOnEnter` (default `true`), Enter submits and Shift+Enter inserts a newline. With `submitOnEnter={false}`, Enter always inserts a newline and only clicking the button submits.
- **Sent feedback** (`showSentFeedback`, opt-in): a synchronous UI affordance — it fires right after `onSubmit` is called, regardless of whether that call succeeds asynchronously. It's rendered with `role="status"` and `aria-live="polite"`, and accepts any `sentFeedbackContent` (a string, or a custom node).
- **Action button shape**: when only `buttonIcon` is set (no `buttonLabel`), the action button uses `Button`'s `squared` layout — a compact, icon-only button matching the real chat input's send button (squared corners, not round, sized by its own padding rather than a fixed box). When `buttonLabel` is set, it falls back to `Button`'s default layout (`size="sm"`), which is the one that renders a text label.
Loading
Loading