Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Checkbox Component with Storybook Integration and Tests #19

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 62 additions & 62 deletions package.json
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./components/Checkbox needs to be added here. It enables consumers of the package to import Checkbox directly:

import { Checkbox } from '@stanfordspezi/spezi-web-design-system/components/Checkbox'

Original file line number Diff line number Diff line change
Expand Up @@ -27,124 +27,124 @@
"default": "./dist/style.css"
},
"./components/Avatar": {
"types":"./dist/components/Avatar.d.ts",
"types": "./dist/components/Avatar.d.ts",
"default": "./dist/components/Avatar.js"
},
"./components/Button": {
"types": "./dist/components/Button.d.ts",
"default": "./dist/components/Button.js"
"default": "./dist/components/Button.js"
},
"./components/Calendar": {
"./components/Calendar": {
"types": "./dist/components/Calendar.d.ts",
"default": "./dist/components/Calendar.js"
"default": "./dist/components/Calendar.js"
},
"./components/Card": {
"types": "./dist/components/Card.d.ts",
"default": "./dist/components/Card.js"
"default": "./dist/components/Card.js"
},
"./components/CopyText": {
"./components/CopyText": {
"types": "./dist/components/CopyText.d.ts",
"default": "./dist/components/CopyText.js"
"default": "./dist/components/CopyText.js"
},
"./components/DataTable": {
"types": "./dist/components/DataTable.d.ts",
"default": "./dist/components/DataTable.js"
"default": "./dist/components/DataTable.js"
},
"./components/DatePicker": {
"./components/DatePicker": {
"types": "./dist/components/DatePicker.d.ts",
"default": "./dist/components/DatePicker.js"
"default": "./dist/components/DatePicker.js"
},
"./components/Dialog": {
"types": "./dist/components/Dialog.d.ts",
"default": "./dist/components/Dialog.js"
"default": "./dist/components/Dialog.js"
},
"./components/DropdownMenu": {
"./components/DropdownMenu": {
"types": "./dist/components/DropdownMenu.d.ts",
"default": "./dist/components/DropdownMenu.js"
"default": "./dist/components/DropdownMenu.js"
},
"./components/EmptyState": {
"types": "./dist/components/EmptyState.d.ts",
"default": "./dist/components/EmptyState.js"
"default": "./dist/components/EmptyState.js"
},
"./components/Error": {
"./components/Error": {
"types": "./dist/components/Error.d.ts",
"default": "./dist/components/Error.js"
"default": "./dist/components/Error.js"
},
"./components/Input": {
"types": "./dist/components/Input.d.ts",
"default": "./dist/components/Input.js"
"default": "./dist/components/Input.js"
},
"./components/Label": {
"./components/Label": {
"types": "./dist/components/Label.d.ts",
"default": "./dist/components/Label.js"
"default": "./dist/components/Label.js"
},
"./components/Pagination": {
"types": "./dist/components/Pagination.d.ts",
"default": "./dist/components/Pagination.js"
"default": "./dist/components/Pagination.js"
},
"./components/Popover": {
"./components/Popover": {
"types": "./dist/components/Popover.d.ts",
"default": "./dist/components/Popover.js"
"default": "./dist/components/Popover.js"
},
"./components/RangeCounter": {
"types": "./dist/components/RangeCounter.d.ts",
"default": "./dist/components/RangeCounter.js"
"default": "./dist/components/RangeCounter.js"
},
"./components/Select": {
"./components/Select": {
"types": "./dist/components/Select.d.ts",
"default": "./dist/components/Select.js"
"default": "./dist/components/Select.js"
},
"./components/Separator": {
"types": "./dist/components/Separator.d.ts",
"default": "./dist/components/Separator.js"
"default": "./dist/components/Separator.js"
},
"./components/SideLabel": {
"./components/SideLabel": {
"types": "./dist/components/SideLabel.d.ts",
"default": "./dist/components/SideLabel.js"
"default": "./dist/components/SideLabel.js"
},
"./components/Switch": {
"types": "./dist/components/Switch.d.ts",
"default": "./dist/components/Switch.js"
"default": "./dist/components/Switch.js"
},
"./components/Table": {
"./components/Table": {
"types": "./dist/components/Table.d.ts",
"default": "./dist/components/Table.js"
"default": "./dist/components/Table.js"
},
"./components/Tabs": {
"types": "./dist/components/Tabs.d.ts",
"default": "./dist/components/Tabs.js"
"default": "./dist/components/Tabs.js"
},
"./components/Textarea": {
"./components/Textarea": {
"types": "./dist/components/Textarea.d.ts",
"default": "./dist/components/Textarea.js"
"default": "./dist/components/Textarea.js"
},
"./components/Toaster": {
"types": "./dist/components/Toaster.d.ts",
"default": "./dist/components/Toaster.js"
"default": "./dist/components/Toaster.js"
},
"./components/Tooltip": {
"./components/Tooltip": {
"types": "./dist/components/Tooltip.d.ts",
"default": "./dist/components/Tooltip.js"
"default": "./dist/components/Tooltip.js"
},
"./molecules/AsideBrandLayout": {
"./molecules/AsideBrandLayout": {
"types": "./dist/molecules/AsideBrandLayout.d.ts",
"default": "./dist/molecules/AsideBrandLayout.js"
},
"./molecules/ConfirmDeleteDialog": {
"types": "./dist/molecules/ConfirmDeleteDialog.d.ts",
"default": "./dist/molecules/ConfirmDeleteDialog.js"
"types": "./dist/molecules/ConfirmDeleteDialog.d.ts",
"default": "./dist/molecules/ConfirmDeleteDialog.js"
},
"./molecules/DashboardLayout": {
"types": "./dist/molecules/DashboardLayout.d.ts",
"default": "./dist/molecules/DashboardLayout.js"
"./molecules/DashboardLayout": {
"types": "./dist/molecules/DashboardLayout.d.ts",
"default": "./dist/molecules/DashboardLayout.js"
},
"./molecules/Notifications": {
"types": "./dist/molecules/Notifications.d.ts",
"default": "./dist/molecules/Notifications.js"
"types": "./dist/molecules/Notifications.d.ts",
"default": "./dist/molecules/Notifications.js"
},
"./molecules/NotFound": {
"types": "./dist/molecules/NotFound.d.ts",
"default": "./dist/molecules/NotFound.js"
"types": "./dist/molecules/NotFound.d.ts",
"default": "./dist/molecules/NotFound.js"
},
"./SpeziProvider": {
"types": "./dist/SpeziProvider.d.ts",
Expand All @@ -158,33 +158,33 @@
"types": "./dist/modules/auth.d.ts",
"default": "./dist/modules/auth.js"
},
"./utils/className": {
"./utils/className": {
"types": "./dist/utils/className.d.ts",
"default": "./dist/utils/className.js"
},
"./utils/date": {
"types": "./dist/utils/date.d.ts",
"default": "./dist/utils/date.js"
"types": "./dist/utils/date.d.ts",
"default": "./dist/utils/date.js"
},
"./utils/file": {
"types": "./dist/utils/file.d.ts",
"default": "./dist/utils/file.js"
"./utils/file": {
"types": "./dist/utils/file.d.ts",
"default": "./dist/utils/file.js"
},
"./utils/misc": {
"types": "./dist/utils/misc.d.ts",
"default": "./dist/utils/misc.js"
"types": "./dist/utils/misc.d.ts",
"default": "./dist/utils/misc.js"
},
"./utils/tailwind": {
"types": "./dist/utils/tailwind.d.ts",
"default": "./dist/utils/tailwind.js"
"./utils/tailwind": {
"types": "./dist/utils/tailwind.d.ts",
"default": "./dist/utils/tailwind.js"
},
"./utils/useOpenState": {
"types": "./dist/utils/useOpenState.d.ts",
"default": "./dist/utils/useOpenState.js"
"types": "./dist/utils/useOpenState.d.ts",
"default": "./dist/utils/useOpenState.js"
},
"./utils/navigator": {
"types": "./dist/utils/navigator.d.ts",
"default": "./dist/utils/navigator.js"
"types": "./dist/utils/navigator.d.ts",
"default": "./dist/utils/navigator.js"
}
},
"main": "dist/spezi-web-design-system.es.js",
Expand Down
40 changes: 40 additions & 0 deletions src/components/Checkbox/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// This source file is part of the Stanford Biodesign Digital Health Spezi Web Design System open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import { type Meta, type StoryObj } from '@storybook/react'
import { fn } from '@storybook/test'
import { useState } from 'react'
import { SideLabel } from '@/components/SideLabel'
import { Checkbox } from './Checkbox'

const meta: Meta<typeof Checkbox> = {
title: 'Components/Checkbox',
component: Checkbox,
args: {
onCheckedChange: fn(),
},
}

export default meta

type Story = StoryObj<typeof Checkbox>

export const Unchecked: Story = { args: { checked: false } }

export const Chekced: Story = { args: { checked: true } }

export const Functional = () => {
const [checked, setChecked] = useState<boolean | 'indeterminate'>(false)
return <Checkbox checked={checked} onCheckedChange={setChecked} />
}

export const Labeled = () => (
<SideLabel label="Show unread only">
<Checkbox checked />
</SideLabel>
)
31 changes: 31 additions & 0 deletions src/components/Checkbox/Checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// This source file is part of the Stanford Biodesign Digital Health Spezi Web Design System open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import { fireEvent, render, screen } from '@testing-library/react'
import { vitest } from 'vitest'
import { Checkbox } from '.'

describe('Checkbox', () => {
it('renders functional Checkbox element', () => {
const onCheckedChange = vitest.fn()

render(
<Checkbox
checked={true}
onCheckedChange={onCheckedChange}
aria-label="Checkbox"
/>,
)

const element = screen.getByLabelText('Checkbox')
fireEvent.click(element)

const newCheckedValue = false
expect(onCheckedChange).toHaveBeenCalledWith(newCheckedValue)
Comment on lines +28 to +29
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like newCheckedValue variable is redundant. expect(onCheckedChange).toHaveBeenCalledWith already tells us we're asserting on new value of checked.

Suggested change
const newCheckedValue = false
expect(onCheckedChange).toHaveBeenCalledWith(newCheckedValue)
expect(onCheckedChange).toHaveBeenCalledWith(false)

})
})
34 changes: 34 additions & 0 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// This source file is part of the Stanford Biodesign Digital Health Spezi Web Design System open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import * as CheckboxPrimitives from '@radix-ui/react-checkbox'
import {
type ComponentPropsWithoutRef,
type ElementRef,
forwardRef,
} from 'react'
import { cn } from '../../utils/className'
import { CheckIcon } from 'lucide-react'

export const Checkbox = forwardRef<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs displayName property. There is ESLint rule to enforce that. Did it fire in your IDE?

ElementRef<typeof CheckboxPrimitives.Root>,
ComponentPropsWithoutRef<typeof CheckboxPrimitives.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitives.Root
className={cn(
'hover:bg-violet3 flex size-[25px] appearance-none items-center justify-center rounded bg-white shadow-[0_0_0_2px_black] outline-none',
Copy link
Collaborator

@arkadiuszbachorski arkadiuszbachorski Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not run this yet, but it seems like we need a bit more work here.

Components have to rely on the standard Tailwind's design tokens and design system custom color tokens. This enables consumers to modify the theme.

Focus states have to be properly handled. focus-ring className should do the trick most likely.

className,
)}
{...props}
ref={ref}
>
<CheckboxPrimitives.Indicator>
<CheckIcon />
</CheckboxPrimitives.Indicator>
</CheckboxPrimitives.Root>
))
9 changes: 9 additions & 0 deletions src/components/Checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//
// This source file is part of the Stanford Biodesign Digital Health Spezi Web Design System open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

export * from './Checkbox'
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
export * from './components/Separator'
export * from './components/SideLabel'
export * from './components/Switch'
export * from './components/Checkbox'

Check warning on line 40 in src/index.ts

View check run for this annotation

Codecov / codecov/patch

src/index.ts#L40

Added line #L40 was not covered by tests
export * from './components/Table'
export * from './components/Tabs'
export * from './components/Textarea'
Expand Down
Loading