import type { Meta, StoryObj } from '@storybook/html';
import { WhatsappEditor, type WhatsappEditorOptions } from '@42/core/whatsapp-editor';
import { Picker } from 'emoji-mart';
import '@42/styles/whatsapp-editor.css';
/** Default emoji set β swap this array (or plug a 3rd-party picker) to change it. */
const DEFAULT_EMOJIS = ['π', 'π', 'β€οΈ', 'π₯', 'π', 'π', 'π', 'β¨', 'π―', 'π', 'π', 'π'];
const HTML = `
`;
/** Build a simple inline emoji grid from a customizable list. */
function gridPicker(emojis: string[]) {
return (container: HTMLElement, insert: (emoji: string) => void): void => {
container.classList.add('c42-whatsapp-editor-panel--grid');
container.innerHTML = emojis
.map((e) => ``)
.join('');
container.addEventListener('click', (ev) => {
const btn = (ev.target as HTMLElement).closest('button');
if (btn) insert(btn.textContent!);
});
};
}
const meta: Meta = {
title: 'Core/Inputs & Forms/WhatsappEditor',
tags: ['autodocs'],
argTypes: {
maxLength: { control: { type: 'number', min: 10, max: 4096 } },
},
args: { maxLength: 4096 },
};
export default meta;
type Story = StoryObj;
/**
* Toolbar + live preview, with the emoji picker anchored bottom-right (closed
* until you tap π). Type `*bold*`, `_italic_`, `~strike~`, `` `code` ``.
*/
export const Default: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
new WhatsappEditor(root, { ...args, renderPicker: gridPicker(DEFAULT_EMOJIS) });
return wrapper;
},
};
/**
* ## Custom emoji set
*
* The emoji selection is fully user-controlled β pass any list (or a 3rd-party
* picker like `emoji-mart`) through `renderPicker`:
*
* ```ts
* const MY_EMOJIS = ['π₯³', 'π€', 'π¦', 'β°', 'β '];
* new WhatsappEditor(root, { renderPicker: gridPicker(MY_EMOJIS) });
* ```
*/
export const CustomEmojiSet: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
const business = ['π₯³', 'π€', 'π¦', 'β°', 'β ', 'β', 'π³', 'π', 'π', 'β'];
new WhatsappEditor(root, { ...args, renderPicker: gridPicker(business) });
return wrapper;
},
};
/** Seeded with formatted markup to show the preview immediately. */
export const Prefilled: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
new WhatsappEditor(root, {
...args,
value: '*Order confirmed!* β \n\n_Tracking_: ~12345~\n\n> Thanks for shopping\n- Fast\n- Secure',
renderPicker: gridPicker(DEFAULT_EMOJIS),
});
return wrapper;
},
};
/** Without the emoji picker β toolbar + preview only. */
export const NoEmoji: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
new WhatsappEditor(root, { ...args, emojis: false });
return wrapper;
},
};
/**
* ## Floating selection menu (bubble toolbar)
*
* Add a `[data-c42-wa-floating]` element containing `[data-c42-wa-command]`
* buttons anywhere inside the editor root. The controller shows it centered
* above the selection whenever you select text, positions it with a mirror-div
* caret measurement, and reflects each marker's active state via `aria-pressed`
* / `data-active`. It hides on collapse, blur, Escape, scroll and resize.
*
* **Select a few words below** to reveal the bubble toolbar.
*
* ```html
*
*
*
*
*
*
*
* ```
*/
export const FloatingMenu: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
new WhatsappEditor(root, {
...args,
value: 'Select this text to reveal the floating toolbar above the selection.',
renderPicker: gridPicker(DEFAULT_EMOJIS),
});
return wrapper;
},
};
/**
* ## Built-in floating menu (`floating: true`)
*
* Don't want to author the bubble markup? Pass `floating: true` and the
* controller injects a default, themed selection toolbar (bold / italic /
* strikethrough / monospace) when you haven't provided your own
* `[data-c42-wa-floating]`. Your markup always wins when present β the flag only
* decides who supplies the UI; the controller owns the logic and events either way.
*
* ```ts
* new WhatsappEditor(root, { floating: true });
* ```
*
* **Select a few words below** β the bubble here is the injected default.
*/
export const BuiltInFloatingMenu: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = HTML;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
// Remove the authored bubble menu so the injected default is what you see.
root.querySelector('[data-c42-wa-floating]')?.remove();
new WhatsappEditor(root, {
...args,
floating: true,
value: 'Select this text β this bubble menu was injected by floating: true.',
renderPicker: gridPicker(DEFAULT_EMOJIS),
});
return wrapper;
},
};
/**
* ## Custom-styled floating menu (your markup, your look)
*
* The bubble menu is just your `[data-c42-wa-floating]` element with
* `[data-c42-wa-command]` buttons β style it however you like. The controller
* only toggles `data-state` / `hidden`, positions it, and sets `aria-pressed` /
* `data-active`, so all the formatting logic and events keep working no matter
* how it looks.
*
* This example skins it as a dark WhatsApp-style pill and highlights the active
* format in green β purely via CSS on a custom class. **Select text** to see it.
*
* ```css
* .my-bubble.c42-whatsapp-editor-floating {
* background: #111b21;
* border: 0;
* border-radius: 999px;
* }
* .my-bubble .c42-whatsapp-editor-command[aria-pressed='true'] {
* background: #25d366;
* color: #07130c;
* }
* ```
*/
export const CustomFloatingMenu: Story = {
render: (args) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = `
${HTML}`;
const root = wrapper.querySelector('[data-c42-whatsapp-editor]')!;
// Tag the authored bubble menu with a class so the CSS above can restyle it.
root.querySelector('[data-c42-wa-floating]')?.classList.add('my-bubble');
new WhatsappEditor(root, {
...args,
value: 'Select me β the bubble menu uses a custom dark pill style.',
renderPicker: gridPicker(DEFAULT_EMOJIS),
});
return wrapper;
},
};
/**
* ## Device preview (opt-in)
*
* Wrap the `[data-c42-wa-preview]` element in `.c42-whatsapp-editor-device` to
* render the live preview on a WhatsApp phone background (the image ships with
* `@42/styles`). It is entirely optional β omit the wrapper to keep the plain
* bubble. Positioning is tunable via CSS custom properties
* (`--c42-wa-device-pad-top`, `--c42-wa-device-pad-x`, `--c42-wa-device-height`).
*
* ```html
*