-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): add clipboard (#2189)
- Loading branch information
1 parent
2ab039a
commit 6b47df9
Showing
63 changed files
with
1,082 additions
and
2 deletions.
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
packages/frameworks/react/src/clipboard/clipboard-context.ts
This file contains 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,10 @@ | ||
import { createContext } from '../create-context' | ||
import { type UseClipboardReturn } from './use-clipboard' | ||
|
||
export interface ClipboardContext extends UseClipboardReturn {} | ||
|
||
export const [ClipboardProvider, useClipboardContext] = createContext<ClipboardContext>({ | ||
name: 'ClipboardContext', | ||
hookName: 'useClipboardContext', | ||
providerName: '<ClipboardProvider />', | ||
}) |
15 changes: 15 additions & 0 deletions
15
packages/frameworks/react/src/clipboard/clipboard-control.tsx
This file contains 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,15 @@ | ||
import { mergeProps } from '@zag-js/react' | ||
import { forwardRef } from 'react' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardControlProps extends HTMLArkProps<'div'> {} | ||
|
||
export const ClipboardControl = forwardRef<HTMLDivElement, ClipboardControlProps>((props, ref) => { | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(api.controlProps, props) | ||
|
||
return <ark.div {...mergedProps} ref={ref} /> | ||
}) | ||
|
||
ClipboardControl.displayName = 'ClipboardControl' |
24 changes: 24 additions & 0 deletions
24
packages/frameworks/react/src/clipboard/clipboard-indicator.tsx
This file contains 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,24 @@ | ||
import { mergeProps } from '@zag-js/react' | ||
import { forwardRef, type ReactNode } from 'react' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardIndicatorProps extends HTMLArkProps<'div'> { | ||
copied?: ReactNode | ||
} | ||
|
||
export const ClipboardIndicator = forwardRef<HTMLDivElement, ClipboardIndicatorProps>( | ||
(props, ref) => { | ||
const { children, copied, ...localProps } = props | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(api.getIndicatorProps({ copied: api.isCopied }), localProps) | ||
|
||
return ( | ||
<ark.div {...mergedProps} ref={ref}> | ||
{api.isCopied ? copied : children} | ||
</ark.div> | ||
) | ||
}, | ||
) | ||
|
||
ClipboardIndicator.displayName = 'Clipb oardIndicator' |
15 changes: 15 additions & 0 deletions
15
packages/frameworks/react/src/clipboard/clipboard-input.tsx
This file contains 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,15 @@ | ||
import { mergeProps } from '@zag-js/react' | ||
import { forwardRef } from 'react' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardInputProps extends HTMLArkProps<'input'> {} | ||
|
||
export const ClipboardInput = forwardRef<HTMLInputElement, ClipboardInputProps>((props, ref) => { | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(api.inputProps, props) | ||
|
||
return <ark.input {...mergedProps} ref={ref} /> | ||
}) | ||
|
||
ClipboardInput.displayName = 'ClipboardInput' |
15 changes: 15 additions & 0 deletions
15
packages/frameworks/react/src/clipboard/clipboard-label.tsx
This file contains 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,15 @@ | ||
import { mergeProps } from '@zag-js/react' | ||
import { forwardRef } from 'react' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardLabelProps extends HTMLArkProps<'label'> {} | ||
|
||
export const ClipboardLabel = forwardRef<HTMLLabelElement, ClipboardLabelProps>((props, ref) => { | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(api.labelProps, props) | ||
|
||
return <ark.label {...mergedProps} ref={ref} /> | ||
}) | ||
|
||
ClipboardLabel.displayName = 'ClipboardLabel' |
37 changes: 37 additions & 0 deletions
37
packages/frameworks/react/src/clipboard/clipboard-root.tsx
This file contains 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 { mergeProps } from '@zag-js/react' | ||
import { forwardRef, type ReactNode } from 'react' | ||
import { createSplitProps } from '../create-split-props' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { runIfFn } from '../run-if-fn' | ||
import type { Assign } from '../types' | ||
import { ClipboardProvider } from './clipboard-context' | ||
import { useClipboard, type UseClipboardProps, type UseClipboardReturn } from './use-clipboard' | ||
|
||
export interface ClipboardRootProps | ||
extends Assign< | ||
HTMLArkProps<'div'>, | ||
{ | ||
children?: ReactNode | ((api: UseClipboardReturn) => ReactNode) | ||
} | ||
>, | ||
UseClipboardProps {} | ||
|
||
export const ClipboardRoot = forwardRef<HTMLDivElement, ClipboardRootProps>((props, ref) => { | ||
const [useClipboardProps, { children, ...localProps }] = createSplitProps<UseClipboardProps>()( | ||
props, | ||
['getRootNode', 'id', 'ids', 'value', 'timeout', 'onCopyStatusChange'], | ||
) | ||
const api = useClipboard(useClipboardProps) | ||
const mergedProps = mergeProps(api.rootProps, localProps) | ||
const view = runIfFn(children, api) | ||
|
||
return ( | ||
<ClipboardProvider value={api}> | ||
<ark.div ref={ref} {...mergedProps}> | ||
{view} | ||
</ark.div> | ||
</ClipboardProvider> | ||
) | ||
}) | ||
|
||
ClipboardRoot.displayName = 'ClipboardRoot' |
17 changes: 17 additions & 0 deletions
17
packages/frameworks/react/src/clipboard/clipboard-trigger.tsx
This file contains 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,17 @@ | ||
import { mergeProps } from '@zag-js/react' | ||
import { forwardRef } from 'react' | ||
import { ark, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardTriggerProps extends HTMLArkProps<'button'> {} | ||
|
||
export const ClipboardTrigger = forwardRef<HTMLButtonElement, ClipboardTriggerProps>( | ||
(props, ref) => { | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(api.triggerProps, props) | ||
|
||
return <ark.button {...mergedProps} ref={ref} /> | ||
}, | ||
) | ||
|
||
ClipboardTrigger.displayName = 'ClipboardTrigger' |
This file contains 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,8 @@ | ||
import { ClipboardControl as Control } from './clipboard-control' | ||
import { ClipboardIndicator as Indicator } from './clipboard-indicator' | ||
import { ClipboardInput as Input } from './clipboard-input' | ||
import { ClipboardLabel as Label } from './clipboard-label' | ||
import { ClipboardRoot as Root } from './clipboard-root' | ||
import { ClipboardTrigger as Trigger } from './clipboard-trigger' | ||
|
||
export { Control, Indicator, Input, Label, Root, Trigger } |
This file contains 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,31 @@ | ||
import { type CopyStatusDetails as ClipboardCopyStatusDetails } from '@zag-js/clipboard' | ||
import { useClipboardContext, type ClipboardContext } from './clipboard-context' | ||
import { ClipboardControl, type ClipboardControlProps } from './clipboard-control' | ||
import { ClipboardIndicator, type ClipboardIndicatorProps } from './clipboard-indicator' | ||
import { ClipboardInput, type ClipboardInputProps } from './clipboard-input' | ||
import { ClipboardLabel, type ClipboardLabelProps } from './clipboard-label' | ||
import { ClipboardRoot, type ClipboardRootProps } from './clipboard-root' | ||
import { ClipboardTrigger, type ClipboardTriggerProps } from './clipboard-trigger' | ||
|
||
export * as Clipboard from './clipboard' | ||
|
||
export { | ||
ClipboardControl, | ||
ClipboardIndicator, | ||
ClipboardInput, | ||
ClipboardLabel, | ||
ClipboardRoot, | ||
ClipboardTrigger, | ||
useClipboardContext, | ||
} | ||
|
||
export type { | ||
ClipboardContext, | ||
ClipboardControlProps, | ||
ClipboardCopyStatusDetails, | ||
ClipboardIndicatorProps, | ||
ClipboardInputProps, | ||
ClipboardLabelProps, | ||
ClipboardRootProps, | ||
ClipboardTriggerProps, | ||
} |
Empty file.
44 changes: 44 additions & 0 deletions
44
packages/frameworks/react/src/clipboard/stories/clipboard.stories.tsx
This file contains 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,44 @@ | ||
import type { Meta } from '@storybook/react' | ||
import { Clipboard } from '../' | ||
import './clipbard.css' | ||
import { CheckIcon, ClipboardCopyIcon } from './icons' | ||
|
||
const meta: Meta = { | ||
title: 'Components / Clipboard', | ||
} | ||
|
||
export default meta | ||
|
||
export const Basic = () => { | ||
return ( | ||
<Clipboard.Root value="https://ark-ui.com"> | ||
<Clipboard.Label>Copy this link</Clipboard.Label> | ||
<Clipboard.Control> | ||
<Clipboard.Input /> | ||
<Clipboard.Trigger> | ||
<Clipboard.Indicator copied={<CheckIcon />}> | ||
<ClipboardCopyIcon /> | ||
</Clipboard.Indicator> | ||
</Clipboard.Trigger> | ||
</Clipboard.Control> | ||
</Clipboard.Root> | ||
) | ||
} | ||
|
||
export const RenderFn = () => { | ||
return ( | ||
<Clipboard.Root value="https://ark-ui.com"> | ||
{(api) => ( | ||
<> | ||
<Clipboard.Label>Copy this link</Clipboard.Label> | ||
<Clipboard.Control> | ||
<Clipboard.Input /> | ||
<Clipboard.Trigger> | ||
{api.isCopied ? <CheckIcon /> : <ClipboardCopyIcon />} | ||
</Clipboard.Trigger> | ||
</Clipboard.Control> | ||
</> | ||
)} | ||
</Clipboard.Root> | ||
) | ||
} |
This file contains 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 @@ | ||
const ClipboardCopyIcon = () => ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
> | ||
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" /> | ||
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" /> | ||
<path d="M16 4h2a2 2 0 0 1 2 2v4" /> | ||
<path d="M21 14H11" /> | ||
<path d="m15 10-4 4 4 4" /> | ||
</svg> | ||
) | ||
|
||
const CheckIcon = () => ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
> | ||
<path d="M20 6 9 17l-5-5" /> | ||
</svg> | ||
) | ||
|
||
export { CheckIcon, ClipboardCopyIcon } |
51 changes: 51 additions & 0 deletions
51
packages/frameworks/react/src/clipboard/tests/clipboard.test.tsx
This file contains 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,51 @@ | ||
import { clipboardAnatomy } from '@ark-ui/anatomy' | ||
// eslint-disable-next-line testing-library/no-manual-cleanup | ||
import { cleanup, render, screen } from '@testing-library/react/pure' | ||
import user from '@testing-library/user-event' | ||
import { Clipboard } from '../' | ||
import { getExports, getParts } from '../../setup-test' | ||
import { CheckIcon, ClipboardCopyIcon } from '../stories/icons' | ||
|
||
const ComponentUnderTest = () => ( | ||
<Clipboard.Root value="https://ark-ui.com"> | ||
<Clipboard.Label>Copy this link</Clipboard.Label> | ||
<Clipboard.Control> | ||
<Clipboard.Input /> | ||
<Clipboard.Trigger> | ||
<Clipboard.Indicator copied={<CheckIcon />}> | ||
<ClipboardCopyIcon /> | ||
</Clipboard.Indicator> | ||
</Clipboard.Trigger> | ||
</Clipboard.Control> | ||
</Clipboard.Root> | ||
) | ||
|
||
describe('Checkbox / Parts & Exports', () => { | ||
afterAll(() => { | ||
cleanup() | ||
}) | ||
|
||
render(<ComponentUnderTest />) | ||
|
||
it.each(getParts(clipboardAnatomy))('should render part %s', async (part) => { | ||
// eslint-disable-next-line testing-library/no-node-access | ||
expect(document.querySelector(part)).toBeInTheDocument() | ||
}) | ||
|
||
it.each(getExports(clipboardAnatomy))('should export %s', async (part) => { | ||
expect(Clipboard[part]).toBeDefined() | ||
}) | ||
}) | ||
|
||
describe('Clipboard', () => { | ||
afterEach(() => { | ||
cleanup() | ||
}) | ||
|
||
it('should copy the value into the clipboard', async () => { | ||
render(<ComponentUnderTest />) | ||
|
||
await user.click(screen.getByRole('button', { name: 'Copy to clipboard' })) | ||
expect(window.navigator.clipboard.writeText).toHaveBeenCalledWith('https://ark-ui.com') | ||
}) | ||
}) |
This file contains 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,24 @@ | ||
import * as clipboard from '@zag-js/clipboard' | ||
import { normalizeProps, useMachine, type PropTypes } from '@zag-js/react' | ||
import { useId } from 'react' | ||
import { useEnvironmentContext } from '../environment' | ||
import { type Optional } from '../types' | ||
|
||
export interface UseClipboardProps extends Optional<clipboard.Context, 'id'> {} | ||
export interface UseClipboardReturn extends clipboard.Api<PropTypes> {} | ||
|
||
export const useClipboard = (props: UseClipboardProps) => { | ||
const initialContext: clipboard.Context = { | ||
id: useId(), | ||
getRootNode: useEnvironmentContext(), | ||
...props, | ||
} | ||
|
||
const context: clipboard.Context = { | ||
...initialContext, | ||
} | ||
|
||
const [state, send] = useMachine(clipboard.machine(initialContext), { context }) | ||
|
||
return clipboard.connect(state, send, normalizeProps) | ||
} |
This file contains 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
This file contains 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
This file contains 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,9 @@ | ||
import { createContext } from '../create-context' | ||
import { type UseClipboardReturn } from './use-clipboard' | ||
|
||
export interface ClipboardContext extends UseClipboardReturn {} | ||
|
||
export const [ClipboardProvider, useClipboardContext] = createContext<ClipboardContext>({ | ||
hookName: 'useClipboardContext', | ||
providerName: '<ClipboardProvider />', | ||
}) |
12 changes: 12 additions & 0 deletions
12
packages/frameworks/solid/src/clipboard/clipboard-control.tsx
This file contains 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,12 @@ | ||
import { mergeProps } from '@zag-js/solid' | ||
import { ark, type ArkComponent, type HTMLArkProps } from '../factory' | ||
import { useClipboardContext } from './clipboard-context' | ||
|
||
export interface ClipboardControlProps extends HTMLArkProps<'div'> {} | ||
|
||
export const ClipboardControl: ArkComponent<'div'> = (props: ClipboardControlProps) => { | ||
const api = useClipboardContext() | ||
const mergedProps = mergeProps(() => api().controlProps, props) | ||
|
||
return <ark.div {...mergedProps} /> | ||
} |
Oops, something went wrong.