diff --git a/package.json b/package.json index 1231f58..7891ce8 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@markuplint/jsx-parser": "^3.11.0", "@markuplint/react-spec": "^3.12.0", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", "@stoplight/prism-cli": "^5.4.0", "@storybook/addon-essentials": "^7.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb9a267..8abf3f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,9 @@ devDependencies: '@radix-ui/react-avatar': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-popover': + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.30)(react@18.2.0) @@ -3282,6 +3285,31 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.30)(react@18.2.0) + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.30)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -3319,6 +3347,29 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@radix-ui/react-id@1.0.1(@types/react@18.2.30)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -3334,6 +3385,41 @@ packages: react: 18.2.0 dev: true + /@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.30)(react@18.2.0) + dev: true + /@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} peerDependencies: @@ -3364,6 +3450,36 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: @@ -3385,6 +3501,49 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.30)(react@18.2.0) + '@types/react': 18.2.30 + '@types/react-dom': 18.2.13 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.13)(@types/react@18.2.30)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: diff --git a/src/components/ui/Popover/index.stories.tsx b/src/components/ui/Popover/index.stories.tsx new file mode 100644 index 0000000..e743d7e --- /dev/null +++ b/src/components/ui/Popover/index.stories.tsx @@ -0,0 +1,41 @@ +import { Button } from '../Button'; + +import { Popover, PopoverTrigger, PopoverContent } from '.'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import { cn } from '@/libs/utils'; + +const meta: Meta = { + component: Popover, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Popover + Content + + ), +}; + +export const WithButton: Story = { + render: () => ( + + + + + + Content + + + ), +}; diff --git a/src/components/ui/Popover/index.test.tsx b/src/components/ui/Popover/index.test.tsx new file mode 100644 index 0000000..ef00e96 --- /dev/null +++ b/src/components/ui/Popover/index.test.tsx @@ -0,0 +1,42 @@ +import { render } from '@testing-library/react'; + +import '@testing-library/jest-dom'; +import { Popover, PopoverTrigger, PopoverContent } from '.'; + +describe('ui/Popoverのテスト', () => { + it('renders trigger text correctly', () => { + const triggerText = 'Trigger'; + const screen = render( + + {triggerText} + Content + + ); + + expect(screen.getByText(triggerText)).toBeInTheDocument(); + }); + + it('renders content correctly', () => { + const content = 'Content'; + const screen = render( + + Trigger + {content} + + ); + + expect(screen.getByText(content)).toBeInTheDocument(); + }); + + it('not renders content by default', () => { + const content = 'Content'; + const screen = render( + + Trigger + {content} + + ); + + expect(() => screen.getByText(content)).toThrow(); + }); +}); diff --git a/src/components/ui/Popover/index.tsx b/src/components/ui/Popover/index.tsx new file mode 100644 index 0000000..1c54c2c --- /dev/null +++ b/src/components/ui/Popover/index.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { forwardRef } from 'react'; + +import * as PopoverPrimitive from '@radix-ui/react-popover'; + +import { cn } from '@/libs/utils'; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent };