-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ee5fcf0
commit b74742d
Showing
11 changed files
with
472 additions
and
11 deletions.
There are no files selected for viewing
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,103 @@ | ||
import { ChevronDownIcon } from '@radix-ui/react-icons' | ||
import { useAtom, useAtomValue } from 'jotai' | ||
import { memo, useEffect, useMemo, useState } from 'react' | ||
import { useCopyToClipboard } from 'usehooks-ts' | ||
|
||
import { figmaRGBToHex, figmaRGBToWebRGB } from '@/entrypoints/utils/convert' | ||
|
||
import { colorMode, currentSelection } from '../store' | ||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '../ui/select' | ||
|
||
export const Colors = memo((props: { minimized?: boolean }) => { | ||
const node = useAtomValue(currentSelection) | ||
const [mode, setMode] = useAtom(colorMode) | ||
|
||
const [paints, setPaints] = useState<Paint[]>([]) | ||
const colors = useMemo( | ||
() => | ||
paints | ||
.filter((p) => p.type === 'SOLID') | ||
.filter((p) => 'color' in p) | ||
.map((p) => ('color' in p ? (mode === 'rgb' ? figmaRGBToWebRGB(p.color) : figmaRGBToHex(p.color)) : null)) | ||
.filter((p) => p !== null), | ||
[mode, paints], | ||
) | ||
|
||
useEffect(() => { | ||
if (figma) { | ||
const res = figma.getSelectionColors() | ||
setPaints(res?.paints || []) | ||
} | ||
}, [node]) | ||
|
||
const [_, copy] = useCopyToClipboard() | ||
|
||
const handleCopy = (text: string) => () => { | ||
copy(text) | ||
.then(() => { | ||
figma.notify('Copied to clipboard') | ||
}) | ||
.catch((error: any) => { | ||
figma.notify('Failed to copy!', { | ||
error: true, | ||
}) | ||
}) | ||
} | ||
|
||
const [showMore, setShowMore] = useState(false) | ||
|
||
return ( | ||
<div | ||
className={`${props.minimized ? 'hidden' : 'block'} p-4 border-t border-#e5e5e5 border-solid font-600 text-13px`} | ||
> | ||
<div className="flex items-center gap-2"> | ||
<span className="flex-1">Colors</span> | ||
|
||
<Select onValueChange={(e: 'rgb') => setMode(e)}> | ||
<SelectTrigger className="w-auto h-auto p-0 uppercase [&_span]:uppercase !shadow-[none] text-xs"> | ||
<SelectValue placeholder={mode} className="uppercase" /> | ||
</SelectTrigger> | ||
<SelectContent className="z-1002 border-1 border-solid border-muted"> | ||
<SelectGroup> | ||
{(['rgb', 'hex'] as const).map((m) => ( | ||
<SelectItem key={m} value={m} className="w-30 uppercase text-xs"> | ||
{m} | ||
</SelectItem> | ||
))} | ||
</SelectGroup> | ||
</SelectContent> | ||
</Select> | ||
</div> | ||
<div | ||
className={`mt-4 flex flex-col max-h-60 overflow-auto scrollbar-hide space-y-.5 ${colors.length === 0 ? 'hidden' : ''}`} | ||
onMouseMove={(e) => e.stopPropagation()} | ||
onWheel={(e) => e.stopPropagation()} | ||
onClick={(e) => e.stopPropagation()} | ||
> | ||
{colors.slice(0, showMore ? colors.length : 3).map((c, index) => ( | ||
<div | ||
className="shrink-0 h-7.5 flex items-center p-1 box-border hover:bg-#e5e5e5/50 rounded-sm cursor-pointer" | ||
onClick={handleCopy(Array.isArray(c) ? `rgba(${c?.join(', ')})` : `${c}`)} | ||
> | ||
<span | ||
className="w-4 h-4 rounded-sm border-1 border-muted border-solid" | ||
style={{ | ||
backgroundColor: Array.isArray(c) ? `rgba(${c?.join(', ')})` : `${c}`, | ||
}} | ||
></span> | ||
<input | ||
type="text" | ||
readOnly | ||
value={Array.isArray(c) ? `rgba(${c?.join(', ')})` : `${c}`} | ||
className="ml-4 font-400 text-xs font-['Inter'] bg-transparent" | ||
/> | ||
</div> | ||
))} | ||
<ChevronDownIcon | ||
className={`mx-auto w-5 h-5 shrink-0 text-#000/50 hover:text-#000 cursor-pointer transition-transform ${colors.length > 3 ? '' : 'hidden'} ${showMore ? 'rotate-180' : ''}`} | ||
onClick={() => setShowMore(!showMore)} | ||
></ChevronDownIcon> | ||
</div> | ||
</div> | ||
) | ||
}) |
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
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,144 @@ | ||
'use client' | ||
|
||
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons' | ||
import * as SelectPrimitive from '@radix-ui/react-select' | ||
import * as React from 'react' | ||
|
||
import { cn } from '../../utils/cn' | ||
|
||
const Select = SelectPrimitive.Root | ||
|
||
const SelectGroup = SelectPrimitive.Group | ||
|
||
const SelectValue = SelectPrimitive.Value | ||
|
||
const SelectTrigger = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.Trigger>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> | ||
>(({ className, children, ...props }, ref) => ( | ||
<SelectPrimitive.Trigger | ||
ref={ref} | ||
className={cn( | ||
'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', | ||
className, | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
<SelectPrimitive.Icon asChild> | ||
<CaretSortIcon className="h-4 w-4 opacity-50" /> | ||
</SelectPrimitive.Icon> | ||
</SelectPrimitive.Trigger> | ||
)) | ||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName | ||
|
||
const SelectScrollUpButton = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> | ||
>(({ className, ...props }, ref) => ( | ||
<SelectPrimitive.ScrollUpButton | ||
ref={ref} | ||
className={cn('flex cursor-default items-center justify-center py-1', className)} | ||
{...props} | ||
> | ||
<ChevronUpIcon /> | ||
</SelectPrimitive.ScrollUpButton> | ||
)) | ||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName | ||
|
||
const SelectScrollDownButton = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> | ||
>(({ className, ...props }, ref) => ( | ||
<SelectPrimitive.ScrollDownButton | ||
ref={ref} | ||
className={cn('flex cursor-default items-center justify-center py-1', className)} | ||
{...props} | ||
> | ||
<ChevronDownIcon /> | ||
</SelectPrimitive.ScrollDownButton> | ||
)) | ||
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName | ||
|
||
const SelectContent = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> | ||
>(({ className, children, position = 'popper', ...props }, ref) => ( | ||
<SelectPrimitive.Portal> | ||
<SelectPrimitive.Content | ||
ref={ref} | ||
className={cn( | ||
'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', | ||
position === 'popper' && | ||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', | ||
className, | ||
)} | ||
position={position} | ||
{...props} | ||
> | ||
<SelectScrollUpButton /> | ||
<SelectPrimitive.Viewport | ||
className={cn( | ||
'p-1', | ||
position === 'popper' && | ||
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]', | ||
)} | ||
> | ||
{children} | ||
</SelectPrimitive.Viewport> | ||
<SelectScrollDownButton /> | ||
</SelectPrimitive.Content> | ||
</SelectPrimitive.Portal> | ||
)) | ||
SelectContent.displayName = SelectPrimitive.Content.displayName | ||
|
||
const SelectLabel = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.Label>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> | ||
>(({ className, ...props }, ref) => ( | ||
<SelectPrimitive.Label ref={ref} className={cn('px-2 py-1.5 text-sm font-semibold', className)} {...props} /> | ||
)) | ||
SelectLabel.displayName = SelectPrimitive.Label.displayName | ||
|
||
const SelectItem = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.Item>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> | ||
>(({ className, children, ...props }, ref) => ( | ||
<SelectPrimitive.Item | ||
ref={ref} | ||
className={cn( | ||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | ||
className, | ||
)} | ||
{...props} | ||
> | ||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center"> | ||
<SelectPrimitive.ItemIndicator> | ||
<CheckIcon className="h-4 w-4" /> | ||
</SelectPrimitive.ItemIndicator> | ||
</span> | ||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> | ||
</SelectPrimitive.Item> | ||
)) | ||
SelectItem.displayName = SelectPrimitive.Item.displayName | ||
|
||
const SelectSeparator = React.forwardRef< | ||
React.ElementRef<typeof SelectPrimitive.Separator>, | ||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> | ||
>(({ className, ...props }, ref) => ( | ||
<SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} /> | ||
)) | ||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName | ||
|
||
export { | ||
Select, | ||
SelectContent, | ||
SelectGroup, | ||
SelectItem, | ||
SelectLabel, | ||
SelectScrollDownButton, | ||
SelectScrollUpButton, | ||
SelectSeparator, | ||
SelectTrigger, | ||
SelectValue, | ||
} |
Oops, something went wrong.