From f1fd843af80f1327df99147214c33e588d1e4ad6 Mon Sep 17 00:00:00 2001 From: Hernan Alvarado <hernanvid123@gmail.com> Date: Fri, 10 Jan 2025 20:15:50 +0000 Subject: [PATCH] feat: add `RadioGroup` component (#76) --- packages/ui-radio-group/package.json | 48 +++++++++++++ packages/ui-radio-group/src/index.tsx | 43 ++++++++++++ .../src/radio-group.stories.tsx | 68 +++++++++++++++++++ packages/ui-radio-group/tsconfig.json | 9 +++ packages/ui-radio-group/tsup.config.ts | 4 ++ packages/ui-radio/src/index.tsx | 4 +- pnpm-lock.yaml | 9 +++ 7 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 packages/ui-radio-group/package.json create mode 100644 packages/ui-radio-group/src/index.tsx create mode 100644 packages/ui-radio-group/src/radio-group.stories.tsx create mode 100644 packages/ui-radio-group/tsconfig.json create mode 100644 packages/ui-radio-group/tsup.config.ts diff --git a/packages/ui-radio-group/package.json b/packages/ui-radio-group/package.json new file mode 100644 index 0000000..ad73974 --- /dev/null +++ b/packages/ui-radio-group/package.json @@ -0,0 +1,48 @@ +{ + "name": "@halvaradop/ui-radio-group", + "version": "0.1.0", + "private": false, + "description": "A customizable Radio Group component for @halvaradop/ui library with Tailwind CSS styling.", + "type": "module", + "scripts": { + "dev": "tsup --watch", + "build": "tsup", + "format": "prettier --write .", + "format:check": "prettier --check .", + "clean": "pnpm clean:build && pnpm clean:modules", + "clean:build": "rm -rf dist", + "clean:modules": "rm -rf node_modules" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/halvaradop/ui.git" + }, + "keywords": [ + "react", + "component", + "tailwindcss", + "react", + "ui", + "ui library" + ], + "author": "Hernan Alvarado <hernanvid123@gmail.com>", + "license": "MIT", + "bugs": { + "url": "https://github.com/halvaradop/ui/issues" + }, + "homepage": "https://github.com/halvaradop/ui#readme", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "dependencies": { + "@halvaradop/ui-core": "workspace:*", + "class-variance-authority": "0.7.0" + } +} diff --git a/packages/ui-radio-group/src/index.tsx b/packages/ui-radio-group/src/index.tsx new file mode 100644 index 0000000..dd2445b --- /dev/null +++ b/packages/ui-radio-group/src/index.tsx @@ -0,0 +1,43 @@ +"use client" +import { ComponentProps, forwardRef, useEffect, useRef } from "react" +import { merge, type ArgsFunction } from "@halvaradop/ui-core" +import { cva, type VariantProps } from "class-variance-authority" + +export type RadioGroupProps<T extends ArgsFunction> = VariantProps<T> & + Omit<ComponentProps<"fieldset">, "children"> & { + children: React.ReactNode + } + +export const radioGroupVariants = cva("flex", { + variants: { + variant: { + row: "flex-row gap-x-5", + column: "flex-col gap-y-1", + }, + }, + defaultVariants: { + variant: "column", + }, +}) + +export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps<typeof radioGroupVariants>>(({ className, variant, name, defaultValue, children, ...props }, ref) => { + // @ts-ignore + const reference = useRef<HTMLFieldSetElement>(ref?.current) + + useEffect(() => { + if (!reference.current) return + const radioButtons = reference.current.querySelectorAll("input[type=radio]") + radioButtons.forEach((radio) => { + if (radio.getAttribute("value") === defaultValue) { + radio.setAttribute("checked", "checked") + } + radio.setAttribute("name", name ?? "default-radio-group") + }) + }, []) + + return ( + <fieldset className={merge(radioGroupVariants({ className, variant }))} defaultValue={defaultValue} ref={reference} {...props}> + {children} + </fieldset> + ) +}) diff --git a/packages/ui-radio-group/src/radio-group.stories.tsx b/packages/ui-radio-group/src/radio-group.stories.tsx new file mode 100644 index 0000000..3569b58 --- /dev/null +++ b/packages/ui-radio-group/src/radio-group.stories.tsx @@ -0,0 +1,68 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { RadioGroup } from "./index.js" +import { Label } from "../../ui-label/src/index.js" +import { Radio } from "../../ui-radio/src/index.js" + +const meta: Meta = { + title: "ui-radio-group", + tags: ["autodocs"], + component: RadioGroup, + parameters: { + layout: "centered", + }, +} satisfies Meta<typeof RadioGroup> + +type Story = StoryObj<typeof meta> + +export const Column: Story = { + render: () => { + return ( + <RadioGroup name="food"> + <Label className="flex items-center gap-x-2"> + <Radio value="pizza" name="food" /> + Pizza + </Label> + <Label className="flex items-center gap-x-2"> + <Radio value="hamburger" name="food" /> + Hamburger + </Label> + </RadioGroup> + ) + }, +} + +export const Row: Story = { + render: () => { + return ( + <RadioGroup variant="row" name="food"> + <Label className="flex items-center gap-x-2"> + <Radio value="pizza" name="food" /> + Pizza + </Label> + <Label className="flex items-center gap-x-2"> + <Radio value="hamburger" name="food" /> + Hamburger + </Label> + </RadioGroup> + ) + }, +} + +export const DefaultChecked: Story = { + render: () => { + return ( + <RadioGroup name="food" defaultValue="pizza"> + <Label className="flex items-center gap-x-2"> + <Radio value="pizza" name="food" /> + Pizza + </Label> + <Label className="flex items-center gap-x-2"> + <Radio value="hamburger" name="food" /> + Hamburger + </Label> + </RadioGroup> + ) + }, +} + +export default meta diff --git a/packages/ui-radio-group/tsconfig.json b/packages/ui-radio-group/tsconfig.json new file mode 100644 index 0000000..4738e10 --- /dev/null +++ b/packages/ui-radio-group/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@halvaradop/ui-core/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "jsx": "react-jsx" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/ui-radio-group/tsup.config.ts b/packages/ui-radio-group/tsup.config.ts new file mode 100644 index 0000000..75ee2d2 --- /dev/null +++ b/packages/ui-radio-group/tsup.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from "tsup" +import { tsupConfig } from "@halvaradop/ui-core/tsup.config.base" + +export default defineConfig(tsupConfig) diff --git a/packages/ui-radio/src/index.tsx b/packages/ui-radio/src/index.tsx index 461f5e4..b10e9d0 100644 --- a/packages/ui-radio/src/index.tsx +++ b/packages/ui-radio/src/index.tsx @@ -50,9 +50,11 @@ const internalVariants = cva("block absolute rounded-full", { export const Radio = ({ className, size, color, name, ...props }: RadioProps<typeof radioVariants & typeof internalVariants>) => { return ( - <label className="flex items-center justify-center relative" htmlFor={name}> + <label className="w-min inline-flex items-center justify-center relative" htmlFor={name}> <input className={merge(radioVariants({ className, size, color }))} type="radio" name={name} {...props} /> <span className={internalVariants({ size, color })} /> </label> ) } + +Radio.displayName = "Radio" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c14097..0bd418e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,6 +160,15 @@ importers: specifier: 0.7.0 version: 0.7.0 + packages/ui-radio-group: + dependencies: + '@halvaradop/ui-core': + specifier: workspace:* + version: link:../ui-core + class-variance-authority: + specifier: 0.7.0 + version: 0.7.0 + packages/ui-submit: dependencies: '@halvaradop/ui-core':