Merge pull request 'Creating a select component using HeadlessUI' (#68) from custom-select into develop
Reviewed-on: http://85.143.176.51:3000/free-land/front-end/pulls/68
This commit is contained in:
commit
2ee7705d5c
3
src/assets/svg/select-arrow.svg
Normal file
3
src/assets/svg/select-arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.1315 0.6875H0.868974C0.561161 0.6875 0.389286 1.0125 0.579911 1.23438L5.71116 7.18437C5.85804 7.35469 6.14085 7.35469 6.28929 7.18437L11.4205 1.23438C11.6112 1.0125 11.4393 0.6875 11.1315 0.6875Z" fill="#262626"/>
|
||||
</svg>
|
After Width: | Height: | Size: 327 B |
57
src/components/Select.stories.tsx
Normal file
57
src/components/Select.stories.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import Select from "./Select";
|
||||
|
||||
export default {
|
||||
// Title inside navigation bar
|
||||
title: "Select",
|
||||
// Component to test
|
||||
component: Select,
|
||||
// Clarifying the way how to process specific
|
||||
// properties of your component and which values
|
||||
// it can accept.
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof Select>;
|
||||
|
||||
/**
|
||||
* This is a way to define a tempalte for your component.
|
||||
*
|
||||
* This template should cover all the states.
|
||||
*
|
||||
* In most cases you should just distruct args attribute
|
||||
* on a returning component.
|
||||
*/
|
||||
|
||||
const Template: ComponentStory<typeof Select> = (args) => {
|
||||
const { options = [{ name: String }] } = args;
|
||||
const [selected, setSelected] = useState(options[0]);
|
||||
return (
|
||||
<div>
|
||||
<Select<any>
|
||||
options={options}
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
displayValueResolver={(options) => options.name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* States of your component */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
options: [
|
||||
{ name: "Wade Cooper" },
|
||||
{ name: "Arlene Mccoy" },
|
||||
{ name: "Devon Webb" },
|
||||
{ name: "Tom Cook" },
|
||||
{ name: "Tanya Fox" },
|
||||
{ name: "Hellen Schmidt" },
|
||||
],
|
||||
};
|
121
src/components/Select.tsx
Normal file
121
src/components/Select.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Imports */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import React from "react";
|
||||
import { Fragment } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
import "../index.css";
|
||||
import { ReactComponent as SelectIcon } from "../assets/svg/select-arrow.svg";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type Props<T> = {
|
||||
options?: T[];
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
value: T;
|
||||
displayValueResolver?: (element: T) => any;
|
||||
onChange: (element: T) => void;
|
||||
} & Omit<React.ComponentPropsWithRef<"select">, "value" | "onChange">;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* styles */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const SelectButtonStyle = `
|
||||
relative w-full
|
||||
cursor-default
|
||||
rounded
|
||||
border border-gray-50
|
||||
outline-8
|
||||
bg-white
|
||||
py-2 pl-3 pr-10 text-left
|
||||
hover:border-gray-300
|
||||
focus:outline-1
|
||||
focus-visible:border-gray-500
|
||||
sm:text-sm
|
||||
`;
|
||||
|
||||
const SelectOptionsStyle = `
|
||||
absolute z-10 mt-1 w-full max-h-56
|
||||
bg-white shadow-lg
|
||||
rounded py-1
|
||||
overflow-auto
|
||||
focus:outline-none
|
||||
text-base
|
||||
sm:text-sm
|
||||
`;
|
||||
|
||||
const SelectIconStyle = `
|
||||
pointer-events-none
|
||||
absolute inset-y-0 right-0
|
||||
flex items-center pr-2
|
||||
`;
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
function Select<T>({
|
||||
className,
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
displayValueResolver,
|
||||
disabled,
|
||||
...props
|
||||
}: Props<T>): JSX.Element {
|
||||
return (
|
||||
<div className={classNames("fixed top-16 w-60", className)}>
|
||||
<Listbox value={value} {...props} onChange={onChange}>
|
||||
<div className="relative mt-1">
|
||||
<Listbox.Button className={`${SelectButtonStyle}`}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<span className="block truncate">{`${
|
||||
displayValueResolver ? displayValueResolver(value) : value
|
||||
}`}</span>
|
||||
<span className={`${SelectIconStyle}`}>
|
||||
<SelectIcon
|
||||
className={`${
|
||||
open ? "rotate-180 transform" : "font-normal"
|
||||
} h-2 w-3`}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
||||
{options.map((option, id) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active, selected }) =>
|
||||
classNames(
|
||||
active ? "text-gray-900 bg-blue-50" : "font-normal ",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9",
|
||||
selected ? "text-gray-900 bg-blue-100" : "font-normal "
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{`${
|
||||
displayValueResolver ? displayValueResolver(option) : option
|
||||
}`}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Select;
|
Loading…
x
Reference in New Issue
Block a user