-
Notifications
You must be signed in to change notification settings - Fork 0
feat(ui/select): pass-through contentProps to override Popover.Content alignment; add docs story #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ui/select): pass-through contentProps to override Popover.Content alignment; add docs story #154
Changes from all commits
e8c29c7
6fa2041
9b037f0
3c0438a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@lambdacurry/forms": patch | ||
| --- | ||
|
|
||
| Add contentProps to UI Select to pass through PopoverPrimitive.Content positioning props (align/side/sideOffset). | ||
| Defaults preserved (align='start', sideOffset=4). New Storybook example and play test demonstrate overriding align to 'end' for right-aligned triggers. | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,86 @@ | ||||||||||||||||||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | ||||||||||||||||||
| import { expect, userEvent, within } from '@storybook/test'; | ||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||
| import { Select, type SelectOption } from '@lambdacurry/forms/ui/select'; | ||||||||||||||||||
|
|
||||||||||||||||||
| const meta: Meta<typeof Select> = { | ||||||||||||||||||
| title: 'UI/Select/Alignment', | ||||||||||||||||||
| component: Select, | ||||||||||||||||||
| parameters: { layout: 'centered' }, | ||||||||||||||||||
| tags: ['autodocs'], | ||||||||||||||||||
| } satisfies Meta<typeof Select>; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default meta; | ||||||||||||||||||
|
|
||||||||||||||||||
| type Story = StoryObj<typeof meta>; | ||||||||||||||||||
|
|
||||||||||||||||||
| const OPTIONS: SelectOption[] = [ | ||||||||||||||||||
| { label: 'Alabama', value: 'AL' }, | ||||||||||||||||||
| { label: 'Alaska', value: 'AK' }, | ||||||||||||||||||
| { label: 'Arizona', value: 'AZ' }, | ||||||||||||||||||
| { label: 'Arkansas', value: 'AR' }, | ||||||||||||||||||
| { label: 'California', value: 'CA' }, | ||||||||||||||||||
| ]; | ||||||||||||||||||
|
|
||||||||||||||||||
| export const RightAlignedWithEndContent: Story = { | ||||||||||||||||||
| name: 'Right-aligned trigger with content align="end"', | ||||||||||||||||||
| args: {}, | ||||||||||||||||||
| render: () => { | ||||||||||||||||||
| const [value, setValue] = useState<string>(''); | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <div className="w-[480px]"> | ||||||||||||||||||
| <div className="flex justify-end"> | ||||||||||||||||||
| <div className="w-[280px]"> | ||||||||||||||||||
| <Select | ||||||||||||||||||
| options={OPTIONS} | ||||||||||||||||||
| placeholder="Choose a state" | ||||||||||||||||||
| contentProps={{ align: 'end' }} | ||||||||||||||||||
| value={value} | ||||||||||||||||||
| onChange={setValue} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ); | ||||||||||||||||||
| }, | ||||||||||||||||||
| play: async ({ canvasElement, step }) => { | ||||||||||||||||||
| const canvas = within(canvasElement); | ||||||||||||||||||
|
|
||||||||||||||||||
| await step('Open the select', async () => { | ||||||||||||||||||
| // Find the trigger by its role and accessible name (which should be the placeholder) | ||||||||||||||||||
| const trigger = await canvas.findByRole('combobox', { name: 'Choose a state' }); | ||||||||||||||||||
| await userEvent.click(trigger); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Wait for popover content to be rendered | ||||||||||||||||||
| await new Promise((r) => setTimeout(r, 100)); | ||||||||||||||||||
| const contentEl = document.body.querySelector('[data-slot="popover-content"]') as HTMLElement | null; | ||||||||||||||||||
| expect(contentEl).toBeTruthy(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Assert alignment override using data attribute we expose | ||||||||||||||||||
| expect(contentEl).toHaveAttribute('data-align', 'end'); | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| await step('Keyboard navigate and select', async () => { | ||||||||||||||||||
| // Focus should be inside the popover; try arrow navigation then enter | ||||||||||||||||||
| await userEvent.keyboard('[ArrowDown]'); | ||||||||||||||||||
| await userEvent.keyboard('[Enter]'); | ||||||||||||||||||
|
|
||||||||||||||||||
|
Comment on lines
+64
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick Use userEvent keyboard tokens with curly braces. Square-bracket tokens are nonstandard and may be flaky. - await userEvent.keyboard('[ArrowDown]');
- await userEvent.keyboard('[Enter]');
+ await userEvent.keyboard('{ArrowDown}');
+ await userEvent.keyboard('{Enter}');📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| // Wait for the selection to be processed | ||||||||||||||||||
| await new Promise((r) => setTimeout(r, 100)); | ||||||||||||||||||
|
|
||||||||||||||||||
| // The trigger should now show the selected option (first item: Alabama) | ||||||||||||||||||
| await expect(canvas.findByRole('combobox', { name: 'Alabama' })).resolves.toBeInTheDocument(); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Re-open and press Escape to close | ||||||||||||||||||
| const trigger = await canvas.findByRole('combobox', { name: 'Alabama' }); | ||||||||||||||||||
| await userEvent.click(trigger); | ||||||||||||||||||
| await userEvent.keyboard('[Escape]'); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Ensure popover content is removed | ||||||||||||||||||
| await new Promise((r) => setTimeout(r, 200)); | ||||||||||||||||||
| const stillOpen = document.body.querySelector('[data-slot="popover-content"]'); | ||||||||||||||||||
| expect(stillOpen).toBeNull(); | ||||||||||||||||||
| }); | ||||||||||||||||||
| }, | ||||||||||||||||||
| }; | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify Storybook 9 runner/test package compatibility
You’re on
storybook@^9.0.6but runner libs may need matching major ranges. Please confirm recommended versions.🏁 Script executed:
Length of output: 22
🌐 Web query:
💡 Result:
Short answer: don't use @storybook/test-runner or @storybook/test with Storybook 9.x. Storybook 9 consolidates @storybook/test into storybook/test and replaces the old test-runner with the new addon-vitest test experience — migrate to addon-vitest (storybook/test for APIs) per the Storybook 9 migration docs. [1][2]
Sources:
Migrate Storybook test packages for Storybook 9
apps/docs/package.json currently pins storybook ^9.0.6 but still lists @storybook/test @8.6.14 and @storybook/test-runner @0.22.1 — Storybook 9 consolidates/renames the test packages and replaces the old test-runner. Remove those devDependencies and migrate tests to the Storybook 9 testing setup (install addon-vitest and use the new storybook/test APIs per the Storybook 9 migration guide).
🤖 Prompt for AI Agents