Skip to content
Closed
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
46 changes: 46 additions & 0 deletions code/core/src/actions/containers/ActionLogger/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest';

import type { ActionDisplay } from '../../models';
import { applyActionToList } from './index';

const makeAction = (id: string, args: any[], limit = 50): ActionDisplay => ({
id,
count: 0,
data: { name: 'onClick', args },
options: { limit },
});

describe('applyActionToList', () => {
it('returns an empty list when limit is zero', () => {
const first = makeAction('1', [1], 0);
const second = makeAction('2', [2], 0);

const next = applyActionToList(applyActionToList([], first), second);

expect(next).toEqual([]);
});

it('keeps the most recent actions when the limit is reached', () => {
const limit = 2;
const first = makeAction('1', [1], limit);
const second = makeAction('2', [2], limit);
const third = makeAction('3', [3], limit);

const next = applyActionToList(applyActionToList(applyActionToList([], first), second), third);

expect(next).toHaveLength(2);
expect(next.map((entry) => entry.id)).toEqual(['2', '3']);
});

it('increments count immutably when the latest action data matches', () => {
const previous = { ...makeAction('1', ['same']), count: 1 };
const incoming = makeAction('2', ['same']);

const next = applyActionToList([previous], incoming);

expect(next).toHaveLength(1);
expect(next[0]).not.toBe(previous);
expect(next[0]).toMatchObject({ id: '1', count: 2 });
expect(previous.count).toBe(1);
});
});
32 changes: 21 additions & 11 deletions code/core/src/actions/containers/ActionLogger/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ const safeDeepEqual = (a: any, b: any): boolean => {
}
};

export const applyActionToList = (
prevActions: ActionDisplay[],
action: ActionDisplay
): ActionDisplay[] => {
const limit = action.options.limit ?? Number.POSITIVE_INFINITY;
if (limit <= 0) {
return [];
}
const previous = prevActions.length ? prevActions[prevActions.length - 1] : null;

if (previous && safeDeepEqual(previous.data, action.data)) {
const updated = [...prevActions];
updated[updated.length - 1] = { ...previous, count: previous.count + 1 };
return updated.slice(-limit);
}

const newAction = { ...action, count: 1 };
return [...prevActions, newAction].slice(-limit);
Comment thread
Maverick-666 marked this conversation as resolved.
};

export default function ActionLogger({ active, api }: ActionLoggerProps) {
const [actions, setActions] = useState<ActionDisplay[]>([]);
const parameter = useParameter<ActionsParameters['actions']>(PARAM_KEY);
Expand All @@ -35,17 +55,7 @@ export default function ActionLogger({ active, api }: ActionLoggerProps) {
}, [api]);

const addAction = useCallback((action: ActionDisplay) => {
setActions((prevActions) => {
const newActions = [...prevActions];
const previous = newActions.length && newActions[newActions.length - 1];
if (previous && safeDeepEqual(previous.data, action.data)) {
previous.count++;
} else {
action.count = 1;
newActions.push(action);
}
return newActions.slice(0, action.options.limit);
});
setActions((prevActions) => applyActionToList(prevActions, action));
}, []);

const handleStoryChange = useCallback(() => {
Expand Down
9 changes: 9 additions & 0 deletions code/core/template/stories/basics.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { global as globalThis } from '@storybook/global';

import { action } from 'storybook/actions';

const optionLimitAction = action('onClick', { limit: 3 });
let optionLimitSequence = 0;

export default {
component: globalThis.__TEMPLATE_COMPONENTS__.Button,
args: {
Expand Down Expand Up @@ -88,6 +91,12 @@ export const OptionPersist = {
export const OptionDepth = {
args: { onClick: action('onClick', { depth: 2 }) },
};
export const OptionLimit = {
args: {
onClick: () => optionLimitAction({ seq: optionLimitSequence++ }),
label: 'Click me repeatedly (limit: 3)',
},
};

export const Disabled = {
args: { onClick: action('onCLick') },
Expand Down
12 changes: 11 additions & 1 deletion docs/essentials/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,20 @@ import { action } from 'storybook/actions';

#### `action`

Type: `(name?: string) => void`
Type: `(name?: string, options?: ActionOptions) => void`

Allows you to create an action that appears in the actions panel of the Storybook UI when clicked. The action function takes an optional name parameter, which is used to identify the action in the UI.

You can optionally pass an options object to control how events are stored in the panel. Supported fields include `limit`, `depth`, and `clearOnStoryChange` (plus serializer-related options from `telejson`).

##### `options.limit`

Type: `number`

Default: `50`

Maximum number of entries retained in the Actions panel for this action. Once the limit is reached, Storybook keeps the **most recent** entries and drops older ones.

{/* prettier-ignore-start */}

<CodeSnippets path="addon-actions-action-function.md" />
Expand Down