-
Notifications
You must be signed in to change notification settings - Fork 0
Components / Popover #132
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
Open
ancientbag
wants to merge
7
commits into
master
Choose a base branch
from
feature/Popover
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Components / Popover #132
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
4028c07
feat: popover
ancientbag 3a246cf
fix: popover trigger children error
ancientbag f81cd0d
Merge branch 'master' into feature/Popover
ancientbag 5e29383
docs: description added to md
ancientbag 34d5a2b
feat(components/Popover): add docs
1175727
Merge branch 'master' into feature/Popover
pixel-fixer d8a8469
package-lock.json
pixel-fixer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| src |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # `@byndyusoft-ui/popover` | ||
|
|
||
| --- | ||
|
|
||
| > React component that displays modal content over clicked element | ||
|
|
||
| ## Installation | ||
|
|
||
| ``` | ||
| npm i @byndyusoft-ui/popover | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "name": "@byndyusoft-ui/popover", | ||
| "version": "0.0.1", | ||
| "description": "Byndyusoft UI popover React Component", | ||
| "keywords": [ | ||
| "byndyusoft", | ||
| "byndyusoft-ui", | ||
| "react", | ||
| "popover" | ||
| ], | ||
| "author": "Byndyusoft Frontend Developer <frontend@byndyusoft.com>", | ||
| "homepage": "https://github.com/Byndyusoft/ui/tree/master/components/popover#readme", | ||
| "license": "Apache-2.0", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/Byndyusoft/ui.git" | ||
| }, | ||
| "scripts": { | ||
| "build": "rollup --config", | ||
| "clean": "rimraf dist", | ||
| "lint": "eslint src --config ../../eslint.config.js", | ||
| "test": "jest --config ../../jest.config.js --roots components/popover/src" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/Byndyusoft/ui/issues" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "dependencies": { | ||
| "@floating-ui/react": "^0.26.26" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import typescript from '@rollup/plugin-typescript'; | ||
| import baseConfig from '../../rollup.base.config'; | ||
|
|
||
| export default { | ||
| ...baseConfig, | ||
| input: ['src/index.ts'], | ||
| plugins: [ | ||
| ...baseConfig.plugins, | ||
| typescript({ tsconfig: './tsconfig.json', exclude: ['src/**/*.stories.tsx', 'src/**/*.tests.tsx', 'node_modules'] }) | ||
| ] | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { Placement, useFloating, useInteractions } from '@floating-ui/react'; | ||
|
|
||
| export interface IPopoverOptions { | ||
| initialOpen?: boolean; | ||
| shouldCloseOnClickOutside?: boolean; | ||
| offset?: number; | ||
| fitContentWidthByContainer?: boolean; | ||
| placement?: Placement; | ||
| open?: boolean; | ||
| onOpenChange?: (open: boolean) => void; | ||
| } | ||
|
|
||
| export interface IPopoverContextValue extends ReturnType<typeof useInteractions>, ReturnType<typeof useFloating> { | ||
| open: boolean; | ||
| setOpen: (arg: boolean) => void; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import React, { createContext, useContext, PropsWithChildren } from 'react'; | ||
| import usePopover from './hooks/usePopover'; | ||
| import { IPopoverOptions, IPopoverContextValue } from './Popover.types'; | ||
|
|
||
| const PopoverContext = createContext<IPopoverContextValue>({} as IPopoverContextValue); | ||
|
|
||
| export const usePopoverContext = (): IPopoverContextValue => { | ||
| const context = useContext(PopoverContext); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
| if (context === undefined) { | ||
| throw new Error('Popover components must be wrapped in PopoverContext'); | ||
| } | ||
|
|
||
| return context; | ||
| }; | ||
|
|
||
| interface IPopoverProviderProps extends PropsWithChildren<IPopoverOptions> {} | ||
|
|
||
| const PopoverProvider = ({ children, ...restOptions }: IPopoverProviderProps): JSX.Element => { | ||
| const value = usePopover({ ...restOptions }); | ||
|
|
||
| return <PopoverContext.Provider value={value}>{children}</PopoverContext.Provider>; | ||
| }; | ||
|
|
||
| export default PopoverProvider; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { Meta, Markdown, Canvas, Source, ArgTypes } from '@storybook/blocks'; | ||
| import Readme from '../../README.md'; | ||
| import * as PopoverStories from './Popover.stories'; | ||
|
|
||
| <Meta title="components/Popover" of={PopoverStories} /> | ||
|
|
||
| <Markdown>{Readme}</Markdown> | ||
|
|
||
| ## Usage | ||
|
|
||
| To use the component in your project you must: | ||
|
|
||
| 1. Import the component where you need it: | ||
|
|
||
| <Source language="javascript" code="import Popover from '@byndyusoft-ui/popover';" /> | ||
|
|
||
| 2. Call the component and give it the desired props, for example: | ||
|
|
||
| - Simple popover | ||
|
|
||
| <Canvas sourceState="shown" of={PopoverStories.Default} /> | ||
|
|
||
| - With portal | ||
|
|
||
| <Canvas sourceState="shown" of={PopoverStories.WithPortal} /> | ||
|
|
||
| - Fit content width by container | ||
|
|
||
| <Canvas sourceState="shown" of={PopoverStories.FitContentWidthByContainer} /> | ||
|
|
||
| - Close on click outside or press esc | ||
|
|
||
| <Canvas sourceState="shown" of={PopoverStories.CloseOnClickOutsideOrPressEsc} /> | ||
|
|
||
| ## Props | ||
|
|
||
| <ArgTypes of={PopoverStories} /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import React from 'react'; | ||
| import type { Meta, StoryObj } from '@storybook/react'; | ||
| import Popover from '../PopoverContext'; | ||
| import PopoverTrigger from '../partials/PopoverTrigger'; | ||
| import PopoverContent from '../partials/PopoverContent'; | ||
|
|
||
| const meta: Meta<typeof Popover> = { | ||
| component: Popover, | ||
| title: 'components/Popover', | ||
| args: { | ||
| initialOpen: undefined, | ||
| shouldCloseOnClickOutside: false, | ||
| placement: 'bottom-start', | ||
| offset: 4, | ||
| open: undefined, | ||
| onOpenChange: undefined | ||
| } | ||
| }; | ||
|
|
||
| type TStory = StoryObj<typeof Popover>; | ||
|
|
||
| export const Default: TStory = { | ||
| render: args => ( | ||
| <Popover {...args}> | ||
| <PopoverTrigger>Click to show</PopoverTrigger> | ||
|
|
||
| <PopoverContent>Click the trigger text to close</PopoverContent> | ||
| </Popover> | ||
| ) | ||
| }; | ||
|
|
||
| export const WithPortal: TStory = { | ||
| render: args => ( | ||
| <Popover {...args}> | ||
| <PopoverTrigger>Click to show</PopoverTrigger> | ||
|
|
||
| <PopoverContent withPortal>Click the trigger text to close</PopoverContent> | ||
| </Popover> | ||
| ) | ||
| }; | ||
|
|
||
| export const FitContentWidthByContainer: TStory = { | ||
| render: args => ( | ||
| <Popover {...args}> | ||
| <PopoverTrigger>Click to show</PopoverTrigger> | ||
|
|
||
| <PopoverContent>Click the trigger text to close</PopoverContent> | ||
| </Popover> | ||
| ), | ||
| args: { | ||
| fitContentWidthByContainer: true | ||
| } | ||
| }; | ||
|
|
||
| export const CloseOnClickOutsideOrPressEsc: TStory = { | ||
| render: args => ( | ||
| <Popover {...args}> | ||
| <PopoverTrigger>Click to show</PopoverTrigger> | ||
|
|
||
| <PopoverContent>Try to click outside or press ESC</PopoverContent> | ||
| </Popover> | ||
| ), | ||
| args: { | ||
| shouldCloseOnClickOutside: true | ||
| } | ||
| }; | ||
|
|
||
| export default meta; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import React from 'react'; | ||
| import { render, screen } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
|
|
||
| import Popover from '../PopoverContext'; | ||
| import PopoverTrigger from '../partials/PopoverTrigger'; | ||
| import PopoverContent from '../partials/PopoverContent'; | ||
|
|
||
| const TRIGGER_LABEL = 'Click to show'; | ||
| const CONTENT_LABEL = 'Click the trigger text to close'; | ||
|
|
||
| describe('components/Popover', () => { | ||
| test('not rendering items when closed', () => { | ||
| userEvent.setup(); | ||
|
|
||
| render( | ||
| <Popover> | ||
| <PopoverTrigger>{TRIGGER_LABEL}</PopoverTrigger> | ||
|
|
||
| <PopoverContent>{CONTENT_LABEL}</PopoverContent> | ||
| </Popover> | ||
| ); | ||
|
|
||
| const buttons = screen.getAllByRole('button'); | ||
|
|
||
| expect(buttons).toHaveLength(1); | ||
|
|
||
| const contentLabelRendered = screen.queryByText(CONTENT_LABEL); | ||
|
|
||
| expect(contentLabelRendered).not.toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('rendering items when opened', async () => { | ||
| userEvent.setup(); | ||
|
|
||
| render( | ||
| <Popover> | ||
| <PopoverTrigger>{TRIGGER_LABEL}</PopoverTrigger> | ||
|
|
||
| <PopoverContent>{CONTENT_LABEL}</PopoverContent> | ||
| </Popover> | ||
| ); | ||
|
|
||
| const buttons = screen.getAllByRole('button'); | ||
|
|
||
| expect(buttons).toHaveLength(1); | ||
|
|
||
| await userEvent.click(buttons[0]); | ||
|
|
||
| const contentLabelRendered = screen.queryByText(CONTENT_LABEL); | ||
|
|
||
| expect(contentLabelRendered).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('rendering items asChild', () => { | ||
| userEvent.setup(); | ||
|
|
||
| render( | ||
| <Popover> | ||
| <PopoverTrigger asChild> | ||
| <a href="/" rel="noopener"> | ||
| {TRIGGER_LABEL} | ||
| </a> | ||
| </PopoverTrigger> | ||
|
|
||
| <PopoverContent>{CONTENT_LABEL}</PopoverContent> | ||
| </Popover> | ||
| ); | ||
|
|
||
| const links = screen.getAllByRole('link'); | ||
|
|
||
| expect(links).toHaveLength(1); | ||
|
|
||
| const buttons = screen.queryAllByRole('button'); | ||
|
|
||
| expect(buttons).toHaveLength(0); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import { useMemo, useState } from 'react'; | ||
| import { | ||
| autoUpdate, | ||
| flip, | ||
| offset as offsetMiddleware, | ||
| shift, | ||
| size, | ||
| useClick, | ||
| useDismiss, | ||
| useFloating, | ||
| useInteractions, | ||
| useRole | ||
| } from '@floating-ui/react'; | ||
| import { IPopoverContextValue, IPopoverOptions } from '../Popover.types'; | ||
|
|
||
| const usePopover = ({ | ||
| initialOpen = false, | ||
| shouldCloseOnClickOutside = false, | ||
| offset, | ||
| fitContentWidthByContainer, | ||
| placement = 'bottom', | ||
| open: controlledOpen, | ||
| onOpenChange: setControlledOpen | ||
| }: IPopoverOptions): IPopoverContextValue => { | ||
| const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen); | ||
|
|
||
| const open = controlledOpen ?? uncontrolledOpen; | ||
| const setOpen = setControlledOpen ?? setUncontrolledOpen; | ||
|
|
||
| const data = useFloating({ | ||
| placement, | ||
| open, | ||
| onOpenChange: setOpen, | ||
| whileElementsMounted: autoUpdate, | ||
| middleware: [ | ||
| offsetMiddleware(offset), | ||
| flip({ | ||
| crossAxis: placement.includes('-'), | ||
| padding: 5 | ||
| }), | ||
| shift({ padding: 5 }), | ||
|
|
||
| fitContentWidthByContainer && | ||
| size({ | ||
| apply({ rects, elements }) { | ||
| Object.assign(elements.floating.style, { | ||
| width: `${rects.reference.width}px` | ||
| }); | ||
| } | ||
| }) | ||
| ] | ||
| }); | ||
|
|
||
| const { context } = data; | ||
|
|
||
| const click = useClick(context, { enabled: controlledOpen === undefined }); | ||
| const dismiss = useDismiss(context, { enabled: shouldCloseOnClickOutside }); | ||
| const role = useRole(context); | ||
|
|
||
| const interactions = useInteractions([click, dismiss, role]); | ||
|
|
||
| return useMemo( | ||
| () => ({ | ||
| open, | ||
| setOpen, | ||
| ...interactions, | ||
| ...data | ||
| }), | ||
| [open, setOpen, interactions, data] | ||
| ); | ||
| }; | ||
|
|
||
| export default usePopover; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export { default as Popover } from './PopoverContext'; | ||
| export { default as PopoverTrigger } from './partials/PopoverTrigger'; | ||
| export { default as PopoverContent } from './partials/PopoverContent'; | ||
| export { default as PopoverClose } from './partials/PopoverClose'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import React from 'react'; | ||
| import { forwardRef, ButtonHTMLAttributes } from 'react'; | ||
| import { usePopoverContext } from '../PopoverContext'; | ||
|
|
||
| const PopoverClose = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>((props, ref) => { | ||
| const { setOpen } = usePopoverContext(); | ||
|
|
||
| const onClickHandler = (event: React.MouseEvent<HTMLButtonElement>): void => { | ||
| props.onClick?.(event); | ||
| setOpen(false); | ||
Oustinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| return <button type="button" ref={ref} {...props} onClick={onClickHandler} />; | ||
| }); | ||
|
|
||
| PopoverClose.displayName = 'PopoverClose'; | ||
|
|
||
| export default PopoverClose; | ||
5 changes: 5 additions & 0 deletions
5
components/popover/src/partials/PopoverContent/PopoverContent.module.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .popoverContent { | ||
| z-index: 100; | ||
|
|
||
| outline: none; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.