Skip to content

Commit

Permalink
Create select components (#151)
Browse files Browse the repository at this point in the history
* Create forerunner select

* Create forernner select two

* Create final select

* Create new components

* Update storybook to use new component

* Add form element

* Run prettier

* Create list box

* Update select story with component library

* Remove radix select package

* Remove custom form componenet

* Add select prefix to compoenents

* Add select icon

* Remove unused import

* Fix linting

* Update to primitive postfix

* Update select component

* Add references and forward refs
  • Loading branch information
Codykilpatrick authored Mar 12, 2024
1 parent a0011de commit 496cf89
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 40 deletions.
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@sentry/nextjs": "7.102.1",
"@spear-ai/logo": "2.1.1",
"autoprefixer": "10.4.18",
"classix": "2.1.36",
"next": "14.1.2",
"next-themes": "0.2.1",
"react": "18.2.0",
Expand Down
68 changes: 28 additions & 40 deletions packages/ui/src/components/select/select.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { CheckIcon } from "@radix-ui/react-icons";
import { useControlledState } from "@react-stately/utils";
import type { Meta, StoryObj } from "@storybook/react";
import { useId } from "react";
import { Form } from "react-aria-components";
import { useIntl } from "react-intl";
import {
Button,
FieldError,
Form,
Label,
ListBox,
ListBoxItem,
Popover,
Select,
SelectButton,
SelectDefaultListBoxItem,
SelectFieldError,
SelectIcon,
SelectLabel,
SelectListBox,
SelectListBoxItem,
SelectPopover,
SelectValue,
} from "react-aria-components";
import { useIntl } from "react-intl";
} from "./select";

const sensorList = [
{ id: "1", name: "Pyramid Array C1" },
Expand Down Expand Up @@ -57,7 +59,6 @@ const PreviewSelect = (properties: {
<div className={`w-full ${isSquished ? "max-w-36" : "max-w-xs"}`}>
<Form className="relative w-full">
<Select
className="group w-full focus:outline-none"
isDisabled={isDisabled}
isInvalid={isInvalid}
isOpen={isAlwaysOpen ? true : undefined}
Expand All @@ -75,15 +76,12 @@ const PreviewSelect = (properties: {
selectedKey={isOptional ? value : undefined}
>
{hasLabel ? (
<Label
className="block select-none text-base/6 text-neutral-12 group-disabled:text-neutral-11 sm:text-sm/6"
htmlFor={sensorFormId}
>
<SelectLabel htmlFor={sensorFormId}>
{intl.formatMessage({
defaultMessage: "Sensor",
id: "SCewMo",
})}
</Label>
</SelectLabel>
) : null}
{hasLabel && hasLabelDescription ? (
<p className="mt-1 text-base/6 text-neutral-11 group-disabled:text-neutral-9 sm:text-sm/6">
Expand All @@ -93,46 +91,36 @@ const PreviewSelect = (properties: {
})}
</p>
) : null}
<Button className="group mt-2 inline-flex h-9 w-full cursor-default select-none items-center justify-between gap-1 rounded-lg border border-transparent bg-white-a-3 pe-2 ps-3.5 text-base leading-none shadow outline outline-offset-0 outline-neutral-a-7 entering:outline-2 entering:outline-primary-a-8 group-invalid:outline-x-negative-a-7 group-disabled:pointer-events-none group-invalid:group-disabled:outline-x-negative-a-6 focus-visible:outline-primary-a-8 theme-dfs:bg-canvas-1 theme-galapago:bg-white theme-dfs:dark:bg-white-a-3 theme-forerunner:dark:bg-black-a-3 theme-galapago:dark:bg-black-a-3 sm:ps-3 sm:text-sm">
<SelectValue className="truncate text-neutral-12 placeholder-shown:text-neutral-11 group-disabled:text-neutral-a-8" />
<span aria-hidden className="text-neutral-11 group-disabled:text-neutral-8">
<CaretSortIcon className="size-5" />
</span>
</Button>
<SelectButton>
<SelectValue />
<SelectIcon />
</SelectButton>
{isInvalid ? (
<FieldError className="mt-2 block text-base/6 text-x-negative-11 group-disabled:opacity-50 sm:text-sm/6">
<SelectFieldError>
{intl.formatMessage({
defaultMessage: "Sensor is invalid.",
id: "JsiKrm",
})}
</FieldError>
</SelectFieldError>
) : null}
<Popover className="isolate min-w-select-trigger-width overflow-auto rounded-xl border-transparent bg-canvas-1 p-1 shadow-lg outline outline-1 outline-offset-0 outline-neutral-a-6 backdrop-blur placement-left:slide-in-from-right-2 placement-right:slide-in-from-left-2 placement-top:slide-in-from-bottom-2 placement-bottom:slide-in-from-top-2 entering:duration-100 entering:animate-in entering:fade-in exiting:duration-75 exiting:animate-out exiting:fade-out exiting:zoom-out-95 theme-forerunner:bg-white-a-3 theme-galapago:bg-white theme-underway:shadow-2xl theme-galapago:dark:bg-black-a-3">
<ListBox className="outline-none">
<ListBoxItem
className="cursor-default select-none rounded-lg py-2.5 pe-5 ps-2 text-base leading-none text-neutral-11 outline-none hover:bg-primary-4 focus:bg-primary-5 focus:outline-none sm:py-1.5 sm:text-sm"
id=""
>
<SelectPopover>
<SelectListBox>
<SelectDefaultListBoxItem id="">
{intl.formatMessage({
defaultMessage: "No sensor",
id: "W2b7y5",
})}
</ListBoxItem>
</SelectDefaultListBoxItem>
{sensorList.map((sensor) => (
<ListBoxItem
className="group/item relative cursor-default select-none rounded-lg py-2.5 pe-7 ps-2 text-base leading-none text-neutral-12 outline-none hover:bg-primary-4 focus:bg-primary-5 sm:py-1.5 sm:text-sm rtl:text-right"
id={sensor.id}
key={sensor.id}
textValue={sensor.name}
>
<SelectListBoxItem id={sensor.id} key={sensor.id} textValue={sensor.name}>
<span>{sensor.name}</span>
<span className="absolute end-1.5 top-2 inline-flex size-4 items-center justify-center opacity-0 group-selected/item:opacity-100">
<CheckIcon className="size-4" />
</span>
</ListBoxItem>
</SelectListBoxItem>
))}
</ListBox>
</Popover>
</SelectListBox>
</SelectPopover>
</Select>
</Form>
</div>
Expand Down
152 changes: 152 additions & 0 deletions packages/ui/src/components/select/select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* eslint-disable react/jsx-props-no-spreading */
import { CaretSortIcon } from "@radix-ui/react-icons";
import { cx } from "classix";
import React, { forwardRef, HTMLAttributes } from "react";
import {
Button as ButtonPrimitive,
FieldError as FieldErrorPrimitive,
Label as LabelPrimitive,
ListBox as ListBoxPrimitive,
ListBoxItem as ListBoxItemPrimitive,
Popover as PopoverPrimitive,
Select as SelectPrimitive,
SelectValue as SelectValuePrimitive,
} from "react-aria-components";

export const Select = React.forwardRef<
React.ElementRef<typeof SelectPrimitive>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx("group w-full focus:outline-none", className);
return <SelectPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

Select.displayName = "Select";

export const SelectListBox = React.forwardRef<
React.ElementRef<typeof ListBoxPrimitive>,
React.ComponentPropsWithoutRef<typeof ListBoxPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx("outline-none", className);
return <ListBoxPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectListBox.displayName = "SelectListBox";

export const SelectLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"block select-none text-base/6 text-neutral-12 group-disabled:text-neutral-11 sm:text-sm/6",
className,
);
return <LabelPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectLabel.displayName = "SelectLabel";

export const SelectDescription = forwardRef<
HTMLParagraphElement,
HTMLAttributes<HTMLParagraphElement> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"mt-1 text-base/6 text-neutral-11 group-disabled:text-neutral-9 sm:text-sm/6",
className,
);
return <p className={mergedClassName} {...properties} ref={reference} />;
});

SelectDescription.displayName = "SelectDescription";

export const SelectIcon = forwardRef<
HTMLSpanElement,
HTMLAttributes<HTMLSpanElement> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx("text-neutral-11 group-disabled:text-neutral-8", className);
return (
<span aria-hidden className={mergedClassName} {...properties} ref={reference}>
<CaretSortIcon className="size-5" />
</span>
);
});

SelectIcon.displayName = "SelectIcon";

export const SelectButton = React.forwardRef<
React.ElementRef<typeof ButtonPrimitive>,
React.ComponentPropsWithoutRef<typeof ButtonPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"group mt-2 inline-flex h-9 w-full cursor-default select-none items-center justify-between gap-1 rounded-lg border border-transparent bg-white-a-3 pe-2 ps-3.5 text-base leading-none shadow outline outline-offset-0 outline-neutral-a-7 entering:outline-2 entering:outline-primary-a-8 group-invalid:outline-x-negative-a-7 group-disabled:pointer-events-none group-invalid:group-disabled:outline-x-negative-a-6 focus-visible:outline-primary-a-8 theme-dfs:bg-canvas-1 theme-galapago:bg-white theme-dfs:dark:bg-white-a-3 theme-forerunner:dark:bg-black-a-3 theme-galapago:dark:bg-black-a-3 sm:ps-3 sm:text-sm",
className,
);
return <ButtonPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectButton.displayName = "SelectButton";

export const SelectValue = React.forwardRef<
React.ElementRef<typeof SelectValuePrimitive>,
React.ComponentPropsWithoutRef<typeof SelectValuePrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"truncate text-neutral-12 placeholder-shown:text-neutral-11 group-disabled:text-neutral-a-8",
className,
);
return <SelectValuePrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectValue.displayName = "SelectValue";

export const SelectFieldError = React.forwardRef<
React.ElementRef<typeof FieldErrorPrimitive>,
React.ComponentPropsWithoutRef<typeof FieldErrorPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"mt-2 block text-base/6 text-x-negative-11 group-disabled:opacity-50 sm:text-sm/6",
className,
);
return <FieldErrorPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectFieldError.displayName = "SelectFieldError";

export const SelectPopover = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"isolate min-w-select-trigger-width overflow-auto rounded-xl border-transparent bg-canvas-1 p-1 shadow-lg outline outline-1 outline-offset-0 outline-neutral-a-6 backdrop-blur placement-left:slide-in-from-right-2 placement-right:slide-in-from-left-2 placement-top:slide-in-from-bottom-2 placement-bottom:slide-in-from-top-2 entering:duration-100 entering:animate-in entering:fade-in exiting:duration-75 exiting:animate-out exiting:fade-out exiting:zoom-out-95 theme-forerunner:bg-white-a-3 theme-galapago:bg-white theme-underway:shadow-2xl theme-galapago:dark:bg-black-a-3",
className,
);
return <PopoverPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectPopover.displayName = "SelectPopover";

export const SelectDefaultListBoxItem = React.forwardRef<
React.ElementRef<typeof ListBoxItemPrimitive>,
React.ComponentPropsWithoutRef<typeof ListBoxItemPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"cursor-default select-none rounded-lg py-2.5 pe-5 ps-2 text-base leading-none text-neutral-11 outline-none hover:bg-primary-4 focus:bg-primary-5 focus:outline-none sm:py-1.5 sm:text-sm",
className,
);
return <ListBoxItemPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectDefaultListBoxItem.displayName = "SelectDefaultListBoxItem";

export const SelectListBoxItem = React.forwardRef<
React.ElementRef<typeof ListBoxItemPrimitive>,
React.ComponentPropsWithoutRef<typeof ListBoxItemPrimitive> & { className?: string | undefined }
>(({ className, ...properties }, reference) => {
const mergedClassName = cx(
"group/item relative cursor-default select-none rounded-lg py-2.5 pe-7 ps-2 text-base leading-none text-neutral-12 outline-none hover:bg-primary-4 focus:bg-primary-5 sm:py-1.5 sm:text-sm rtl:text-right",
className,
);
return <ListBoxItemPrimitive className={mergedClassName} {...properties} ref={reference} />;
});

SelectListBoxItem.displayName = "SelectListBoxItem";
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5319,6 +5319,7 @@ __metadata:
"@types/react": "npm:18.2.63"
"@types/react-dom": "npm:18.2.19"
autoprefixer: "npm:10.4.18"
classix: "npm:2.1.36"
eslint: "npm:8.57.0"
eslint-config-prettier: "npm:9.1.0"
graphql: "npm:16.8.1"
Expand Down Expand Up @@ -8867,6 +8868,13 @@ __metadata:
languageName: node
linkType: hard

"classix@npm:2.1.36":
version: 2.1.36
resolution: "classix@npm:2.1.36"
checksum: 10/365bf02ac4cb9ee586923fa401721f9d53b5ad9359fa61c49d6b66bef3e97f10ea5671050c1bb2e2f8352e2e9d60f351ac00486d4920f160fce821b3285bc899
languageName: node
linkType: hard

"clean-css@npm:^5.2.2":
version: 5.3.3
resolution: "clean-css@npm:5.3.3"
Expand Down

0 comments on commit 496cf89

Please sign in to comment.