diff --git a/.storybook/output.css b/.storybook/output.css index a45da43..9c4e46e 100644 --- a/.storybook/output.css +++ b/.storybook/output.css @@ -735,14 +735,6 @@ select { right: 0px; } -.right-4 { - right: 1rem; -} - -.top-4 { - top: 1rem; -} - .top-1\/2 { top: 50%; } @@ -751,30 +743,14 @@ select { left: 50%; } -.top-1 { - top: 0.25rem; -} - -.right-2 { - right: 0.5rem; -} - -.right-1 { - right: 0.25rem; -} - -.top-2 { - top: 0.5rem; +.top-3 { + top: 0.75rem; } .right-3 { right: 0.75rem; } -.top-3 { - top: 0.75rem; -} - .isolate { isolation: isolate; } @@ -795,6 +771,10 @@ select { margin-top: 0.5rem; } +.ml-2 { + margin-left: 0.5rem; +} + .box-border { box-sizing: border-box; } @@ -811,10 +791,6 @@ select { display: inline-flex; } -.grid { - display: grid; -} - .h-full { height: 100%; } @@ -823,24 +799,32 @@ select { height: 2px; } -.w-full { - width: 100%; +.h-10 { + height: 2.5rem; } -.w-96 { - width: 24rem; +.max-h-96 { + max-height: 24rem; } .w-56 { width: 14rem; } +.w-full { + width: 100%; +} + +.w-96 { + width: 24rem; +} + .min-w-0 { min-width: 0px; } -.max-w-lg { - max-width: 32rem; +.min-w-\[8rem\] { + min-width: 8rem; } .-translate-x-2\/4 { @@ -853,28 +837,28 @@ select { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.translate-x-\[-50\%\] { +.-translate-x-1\/2 { --tw-translate-x: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.translate-y-\[-50\%\] { +.-translate-y-1\/2 { --tw-translate-y: -50%; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.-translate-x-1\/2 { - --tw-translate-x: -50%; +.transform { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } -.-translate-y-1\/2 { - --tw-translate-y: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +.cursor-default { + cursor: default; } -.transform { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } .items-center { @@ -889,10 +873,6 @@ select { justify-content: space-between; } -.gap-4 { - gap: 1rem; -} - .gap-x-2 { -moz-column-gap: 0.5rem; column-gap: 0.5rem; @@ -934,30 +914,26 @@ select { border-radius: 0.5rem; } -.rounded-sm { - border-radius: 0.125rem; +.rounded-full { + border-radius: 9999px; } .rounded-md { border-radius: 0.375rem; } -.rounded-full { - border-radius: 9999px; -} - .border-2 { border-width: 2px; } -.border { - border-width: 1px; -} - .border-0 { border-width: 0px; } +.border { + border-width: 1px; +} + .border-mono-border { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -988,10 +964,6 @@ select { background-color: rgb(209 213 219 / var(--tw-bg-opacity)); } -.bg-black\/80 { - background-color: rgb(0 0 0 / 0.8); -} - .bg-black { --tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity)); @@ -1005,14 +977,14 @@ select { --tw-bg-opacity: 0.5; } -.p-0 { - padding: 0px; -} - .p-1 { padding: 0.25rem; } +.p-0 { + padding: 0px; +} + .p-2 { padding: 0.5rem; } @@ -1102,6 +1074,14 @@ select { padding-right: 0.75rem; } +.pr-2 { + padding-right: 0.5rem; +} + +.pl-8 { + padding-left: 2rem; +} + .text-left { text-align: left; } @@ -1162,13 +1142,9 @@ select { opacity: 0; } -.opacity-70 { - opacity: 0.7; -} - -.shadow-lg { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } @@ -1177,14 +1153,18 @@ select { outline-offset: 2px; } -.transition-opacity { - transition-property: opacity; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; +@keyframes enter { + from { + opacity: var(--tw-enter-opacity, 1); + transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0)); + } } -.duration-200 { - transition-duration: 200ms; +@keyframes exit { + to { + opacity: var(--tw-exit-opacity, 1); + transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0)); + } } html { @@ -1211,8 +1191,14 @@ html { background-color: rgb(63 63 70 / var(--tw-bg-opacity)); } -.hover\:opacity-100:hover { - opacity: 1; +.hover\:bg-mono-hover:hover { + --tw-bg-opacity: 1; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)); +} + +.hover\:bg-mono-primary:hover { + --tw-bg-opacity: 1; + background-color: rgb(39 39 42 / var(--tw-bg-opacity)); } .focus\:border-mono-primary:focus { @@ -1256,10 +1242,22 @@ html { --tw-ring-offset-width: 2px; } -.disabled\:pointer-events-none:disabled { +.disabled\:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +.disabled\:opacity-50:disabled { + opacity: 0.5; +} + +.data-\[disabled\]\:pointer-events-none[data-disabled] { pointer-events: none; } +.data-\[disabled\]\:text-mono-text\/50[data-disabled] { + color: rgb(107 114 128 / 0.5); +} + .data-\[disabled\]\:opacity-50[data-disabled] { opacity: 0.5; } @@ -1268,16 +1266,56 @@ html { outline-style: solid; } -.data-\[focus\]\:outline-2[data-focus] { - outline-width: 2px; +.data-\[state\=open\]\:animate-in[data-state=open] { + animation-name: enter; + animation-duration: 150ms; + --tw-enter-opacity: initial; + --tw-enter-scale: initial; + --tw-enter-rotate: initial; + --tw-enter-translate-x: initial; + --tw-enter-translate-y: initial; } -.data-\[focus\]\:outline-offset-2[data-focus] { - outline-offset: 2px; +.data-\[state\=closed\]\:animate-out[data-state=closed] { + animation-name: exit; + animation-duration: 150ms; + --tw-exit-opacity: initial; + --tw-exit-scale: initial; + --tw-exit-rotate: initial; + --tw-exit-translate-x: initial; + --tw-exit-translate-y: initial; } -.data-\[focus\]\:outline-blue-500[data-focus] { - outline-color: #3b82f6; +.data-\[state\=closed\]\:fade-out-0[data-state=closed] { + --tw-exit-opacity: 0; +} + +.data-\[state\=open\]\:fade-in-0[data-state=open] { + --tw-enter-opacity: 0; +} + +.data-\[state\=closed\]\:zoom-out-95[data-state=closed] { + --tw-exit-scale: .95; +} + +.data-\[state\=open\]\:zoom-in-95[data-state=open] { + --tw-enter-scale: .95; +} + +.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom] { + --tw-enter-translate-y: -0.5rem; +} + +.data-\[side\=left\]\:slide-in-from-right-2[data-side=left] { + --tw-enter-translate-x: 0.5rem; +} + +.data-\[side\=right\]\:slide-in-from-left-2[data-side=right] { + --tw-enter-translate-x: -0.5rem; +} + +.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top] { + --tw-enter-translate-y: 0.5rem; } @media (prefers-reduced-motion: no-preference) { @@ -1293,10 +1331,6 @@ html { } @media (min-width: 640px) { - .sm\:rounded-lg { - border-radius: 0.5rem; - } - .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; diff --git a/package-lock.json b/package-lock.json index 4ec4738..b5f02e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-primitive": "^1.0.3", "@radix-ui/react-select": "^2.0.0", @@ -60,6 +61,7 @@ "storybook": "^7.6.7", "tailwind-merge": "^1.11.0", "tailwindcss": "^3.2.4", + "tailwindcss-animate": "^1.0.7", "ts-jest": "^29.0.5", "tsup": "^6.5.0", "typescript": "^5.1.6", @@ -4259,6 +4261,29 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-label": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz", + "integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz", @@ -24263,6 +24288,15 @@ "postcss": "^8.0.9" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tailwindcss/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", diff --git a/package.json b/package.json index f0fd621..e4b95e4 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "storybook": "^7.6.7", "tailwind-merge": "^1.11.0", "tailwindcss": "^3.2.4", + "tailwindcss-animate": "^1.0.7", "ts-jest": "^29.0.5", "tsup": "^6.5.0", "typescript": "^5.1.6", @@ -79,6 +80,7 @@ "dependencies": { "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-primitive": "^1.0.3", "@radix-ui/react-select": "^2.0.0", diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx new file mode 100644 index 0000000..a71f73e --- /dev/null +++ b/src/components/Select/Select.stories.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { Select, SelectItem } from "./Select"; + +const meta: Meta = { + title: "UI/Select", + component: Select, +}; + +export default meta; +type Story = StoryObj; + +const options = [ + { label: "Banana", value: "banana" }, + { label: "Apple", value: "apple" }, + { label: "Pear", value: "pear" }, +]; + +export const Default: Story = { + render: () => ( + + ), +}; diff --git a/src/components/Select/Select.styles.ts b/src/components/Select/Select.styles.ts new file mode 100644 index 0000000..5811d6f --- /dev/null +++ b/src/components/Select/Select.styles.ts @@ -0,0 +1,79 @@ +import { cva } from "class-variance-authority"; + +const base = [ + "inline-flex", + "items-center", + "justify-between", + "border-2", + "border-mono-border", + "py-1.5", + "px-2", + "rounded-lg", + "focus:outline-none", + "focus:border-mono-primary", +]; + +const viewport = ["p-1"]; + +const content = [ + "relative", + "max-h-96", + "min-w-[8rem]", + "py-1.5", + "px-2", + "border-2", + "border-mono-border", + "overflow-hidden", + "rounded-lg", + "bg-white", + + // Animation + "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", +]; + +const scroll = [ + "flex", + "cursor-default", + "items-center", + "justify-center", + "py-1", +]; + +const indicator = [ + "absolute", + "left-0", + "inline-flex", + "items-center", + "justify-center", +]; + +const selectStyles = cva(base); + +const item = [ + "relative", + "flex", + "w-full", + "cursor-default", + "select-none", + "items-center", + "py-1.5", + "pr-2", + "pl-8", + "data-[disabled]:pointer-events-none", + "data-[disabled]:text-mono-text/50", + "focus:outline-none", + "focus:border-mono-primary", +]; + +const itemStyles = cva(item); + +export { selectStyles, itemStyles, viewport, content, indicator, scroll }; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index d36c336..29d0a91 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,8 +1,62 @@ import * as React from "react"; import * as $ from "@radix-ui/react-select"; +import { Label } from "@radix-ui/react-label"; +import { FaCheck, FaChevronDown, FaChevronUp } from "react-icons/fa"; +import * as T from "./Select.types"; +import * as styles from "./Select.styles"; +import { cx } from "../../utils"; +import { Box } from "../Box"; -const Select = () => { - return
Select
; -}; +const Select = React.forwardRef( + ({ children, className, ...props }, ref) => { + const classes = cx([styles.selectStyles()], className); + console.log("classes", classes); + return ( + + + + <$.Root> + <$.Trigger id="select" {...props} className={classes} ref={ref}> + <$.Value placeholder={props.placeholder} /> + <$.Icon className={cx(["ml-2"])} asChild> + + + + <$.Portal> + <$.Content className={cx([styles.content])} position="popper"> + <$.ScrollUpButton className={cx([styles.scroll])}> + + -export { Select }; + <$.Viewport className={cx([styles.viewport])}> + {children} + + + <$.ScrollDownButton className={cx([styles.scroll])}> + + + <$.Arrow /> + + + + + + ); + } +); + +const SelectItem = React.forwardRef( + ({ children, className, ...props }, forwardedRef) => { + const classes = cx([styles.itemStyles()], className); + return ( + <$.Item {...props} ref={forwardedRef}> + <$.ItemText>{children} + <$.ItemIndicator className={cx([styles.indicator])}> + + {" "} + + ); + } +); + +export { Select, SelectItem }; diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts new file mode 100644 index 0000000..7a537fd --- /dev/null +++ b/src/components/Select/Select.types.ts @@ -0,0 +1,32 @@ +import * as React from "react"; +import * as $ from "@radix-ui/react-select"; +import { VariantProps } from "class-variance-authority"; +import { selectStyles } from "./Select.styles"; + +type SelectOption = { + label: string; + value: string; +}; + +type SelectSection = { + label: string; + value: SelectOption[]; +}; + +type SelectElement = React.ElementRef; + +type SelectProps = React.ComponentPropsWithoutRef & + VariantProps & {}; + +type SelectItemElement = React.ElementRef; + +type SelectItemProps = React.ComponentPropsWithoutRef & {}; + +export { + SelectOption, + SelectSection, + SelectElement, + SelectProps, + SelectItemElement, + SelectItemProps, +}; diff --git a/tailwind.config.js b/tailwind.config.js index cb04a1e..12a42d2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -28,6 +28,9 @@ module.exports = { "mono-text": { DEFAULT: colors.gray["500"], }, + "mono-hover": { + DEFAULT: colors.gray[400], + }, "mono-rounded": { DEFAULT: defaultTheme.borderRadius["lg"], }, @@ -38,5 +41,5 @@ module.exports = { }, }, }, - plugins: [require("@tailwindcss/forms")], + plugins: [require("@tailwindcss/forms"), require("tailwindcss-animate")], };