Skip to content

Commit

Permalink
WIP: added FormField Checkbox
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaap-Hein Wester committed Nov 21, 2024
1 parent 3636be3 commit cf5127d
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/components-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"clean": "rimraf dist/ pages/",
"lint": "tsc --project ./tsconfig.json --noEmit && tsc --noEmit --project ./tsconfig.test.json",
"test": "mkdirp pages && cross-env NODE_OPTIONS=--experimental-vm-modules jest --verbose",
"watch:components": "vite build --watch",
"watch:test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --verbose --watch"
},
"main": "./dist/index.umd.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/* stylelint-disable selector-class-pattern */
:root {
--lux-form-field-checkbox-inner-column-gap: var(--lux-space-100);
--lux-form-field-checkbox-inner-padding-inline-start: var(--lux-space-200, 0.5rem);
--lux-form-field-checkbox-inner-padding-inline-end: var(--lux-space-200, 0.5rem);
--lux-form-field-checkbox-inner-padding-block-start: var(--lux-space-200, 0.5rem);
--lux-form-field-checkbox-inner-padding-block-end: var(--lux-space-200, 0.5rem);
--lux-form-field-checkbox-inner-border-radius: var(--lux-border-radius-default);
--lux-form-field-checkbox-inner-border-width: var(--lux-border-width-default, 1px);
--lux-form-field-checkbox-inner-border-style: solid;
--lux-form-field-checkbox-inner-border-color: var(--lux-color-brand-default);
--lux-form-field-checkbox-inner-background-color: var(--lux-color-none);
--lux-form-field-checkbox-invalid-inner-border-color: var(--lux-color-feedback-error-default);
--lux-form-field-checkbox-invalid-inner-background-color: var(--lux-color-none);
--lux-form-field-checkbox-invalid-inner-row-gap: var(--lux-space-100);
--lux-form-field-checkbox-hover-inner-border-color: var(--lux-color-brand-default);
--lux-form-field-checkbox-hover-inner-background-color: var(--lux-color-brand-subdued);
--lux-form-field-checkbox-active-inner-border-color: var(--lux-color-brand-default);
--lux-form-field-checkbox-active-inner-background-color: var(--lux-color-none);
--lux-form-field-checkbox-focus-visible-inner-border-color: var(--lux-color-brand-default);
--lux-form-field-checkbox-focus-visible-background-color: var(--lux-color-none);
--lux-form-field-checkbox-disabled-inner-border-color: var(--lux-color-border-subdued);
--lux-form-field-checkbox-disabled-inner-background-color: var(--lux-color-none);
}

.lux-form-field-checkbox--robbert {
--utrecht-checkbox-size: 24px;
--utrecht-checkbox-margin-inline-end: 12px;
--lux-indent: calc(var(--utrecht-checkbox-size) + var(--utrecht-checkbox-margin-inline-end));

position: relative;
margin-inline-start: var(--lux-indent);

/* FIXME: remove */
outline: 1pt dotted gold;
outline-offset: 2px;

.utrecht-form-label.utrecht-form-label--checkbox {
display: block;
margin-inline-start: revert;
text-indent: calc(-1 * var(--lux-indent));

&::before {
position: absolute;

/* stylelint-disable property-disallowed-list */
top: 0;
right: 0;
bottom: 0;
left: calc(-1 * var(--lux-indent));
border: var(--lux-form-field-checkbox-inner-border-width) var(--lux-form-field-checkbox-inner-border-style)
var(--lux-form-field-checkbox-inner-border-color);
border-radius: var(--lux-form-field-checkbox-inner-border-radius);
pointer-events: none;
content: "";

/* stylelint-enable property-disallowed-list */
}
}

.utrecht-checkbox {
margin-inline-end: var(--lux-form-field-checkbox-inner-column-gap);
}

.utrecht-form-field-description {
text-indent: -0.25rem;
}

.utrecht-form-field-error-message {
text-indent: calc(-1 * var(--lux-indent));
}
}

.lux-form-field-checkbox {
/* prettier-ignore */
--_lux-checkbox-size: calc(var(--utrecht-checkbox-size) + var(--utrecht-checkbox-margin-inline-end) + var(--lux-form-field-checkbox-inner-padding-inline-start));
--_lux-column-gap: var(--lux-form-field-checkbox-inner-column-gap, var(--utrecht-checkbox-margin-inline-end, 12px));

position: relative;
grid-template-columns: var(--_lux-checkbox-size) 100fr;
grid-template-areas:
"input label"
". description"
"error-message error-message";
gap: 0 var(--_lux-column-gap);

/* FIXME: remove */
outline: 1pt dotted gold;
outline-offset: 2px;

&::before {
grid-row-start: label;
grid-row-end: description;
grid-column-start: input;
grid-column-end: description;
border: var(--lux-form-field-checkbox-inner-border-width) var(--lux-form-field-checkbox-inner-border-style)
var(--lux-form-field-checkbox-inner-border-color);
border-radius: var(--lux-form-field-checkbox-inner-border-radius);
pointer-events: none;
content: "";
}

&--disabled::before {
border-color: var(--lux-form-field-checkbox-disabled-inner-border-color);
background-color: var(--lux-form-field-checkbox-disabled-inner-background-color);
}

&--invalid::before {
border-color: var(--lux-form-field-checkbox-invalid-inner-border-color);
background-color: var(--lux-form-field-checkbox-invalid-inner-background-color);
}

&:has(:focus-visible)::before {
--_utrecht-focus-ring-box-shadow: 0 0 0 var(--utrecht-focus-outline-width, 0)
var(--utrecht-focus-inverse-outline-color, transparent);

/* TODO: maybe use utrecht mixin */
outline-color: var(--utrecht-focus-outline-color, revert);
outline-style: var(--utrecht-focus-outline-style, revert);
outline-width: var(--utrecht-focus-outline-width, revert);
outline-offset: var(--utrecht-focus-outline-offset, revert);
box-shadow: var(--_utrecht-focus-ring-box-shadow);
border-color: var(--lux-form-field-checkbox-focus-visible-inner-border-color);
background-color: var(--lux-form-field-checkbox-focus-visible-inner-background-color);
}

.utrecht-checkbox:focus-visible {
outline: none;
}

&:not(#{&}--disabled):has(.utrecht-form-field__input .utrecht-checkbox:hover)::before {
border-color: var(--lux-form-field-checkbox-hover-inner-border-color);
background-color: var(--lux-form-field-checkbox-hover-inner-background-color);
}

/* TODO: only when block prop is set?? */
&__label::before {
position: absolute;
inset-block-end: 0;
inset-block-start: 0;
inset-inline-end: 0;
inset-inline-start: 0;
pointer-events: visible;

/* outline: 1mm dashed salmon; */
content: "";
}

.utrecht-form-field__label,
.utrecht-form-field__input {
padding-block-start: var(--lux-form-field-checkbox-inner-padding-block-start);
}

.utrecht-form-field__label + .utrecht-form-field__input,
.utrecht-form-field__description {
padding-block-end: var(--lux-form-field-checkbox-inner-padding-block-end);
}

.utrecht-form-field__input {
padding-inline-start: var(--lux-form-field-checkbox-inner-padding-inline-start);
}

.utrecht-form-field__error-message {
padding-block-start: var(--lux-form-field-checkbox-invalid-inner-row-gap);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import clsx from 'clsx';
import { ReactNode, useId } from 'react';
import { LuxCheckbox } from '../checkbox/Checkbox';
import { LuxFormField, LuxFormFieldProps } from '../form-field/FormField';
import {
LuxFormFieldDescription,
type LuxFormFieldDescriptionAppearance,
} from '../form-field-description/FormFieldDescription';
import { LuxFormFieldErrorMessage } from '../form-field-error-message/FormFieldErrorMessage';
import { LuxFormFieldLabel } from '../form-field-label/FormFieldLabel';
import './FormFieldCheckbox.scss';

export type LuxFormFieldCheckboxProps = LuxFormFieldProps & {
label: ReactNode;
description: ReactNode;
errorMessage: ReactNode;
disabled: boolean;
invalid: boolean;
appearance: LuxFormFieldDescriptionAppearance;
distanced: boolean;
children: any;
restProps: any;
};

export const LuxFormFieldCheckbox = ({
label,
description,
errorMessage,
disabled,
invalid,
appearance,
distanced,
children,
...restProps
}: LuxFormFieldCheckboxProps) => {
const inputId = useId();
const descriptionId = useId();
const errorMessageId = useId();

const labelNode =
typeof label === 'string' ? (
<LuxFormFieldLabel
type="checkbox"
disabled={disabled}
htmlFor={inputId}
className="lux-form-field-checkbox__label"
>
{label}
</LuxFormFieldLabel>
) : (
label
);
const descriptionNode =
typeof description === 'string' && description !== '' ? (
<LuxFormFieldDescription
appearance={invalid ? 'invalid' : appearance}
id={descriptionId}
className="lux-form-field-checkbox__description"
>
<label htmlFor={inputId}>{description}</label>
</LuxFormFieldDescription>
) : (
description
);
const errorMessageNode =
typeof errorMessage === 'string' ? (
<LuxFormFieldErrorMessage
distanced={distanced}
id={errorMessageId}
className="lux-form-field-checkbox__error-message"
>
{errorMessage}
</LuxFormFieldErrorMessage>
) : (
errorMessage
);

return (
<>
<LuxFormField type="custom" className={clsx('lux-form-field-checkbox--robbert')} invalid={invalid} {...restProps}>
<LuxFormFieldLabel type="checkbox">
<LuxCheckbox />
{label}
<LuxFormFieldDescription>{description}</LuxFormFieldDescription>
</LuxFormFieldLabel>
<LuxFormFieldErrorMessage>{errorMessage}</LuxFormFieldErrorMessage>
{children}
</LuxFormField>
<hr />
<LuxFormField
type="checkbox"
label={labelNode}
description={descriptionNode}
errorMessage={errorMessageNode}
invalid={invalid}
input={<LuxCheckbox id={inputId} disabled={disabled} invalid={invalid} />}
className={clsx('lux-form-field-checkbox', {
'lux-form-field-checkbox--invalid': invalid,
'lux-form-field-checkbox--disabled': disabled,
})}
{...restProps}
>
{children}
</LuxFormField>
</>
);
};
1 change: 1 addition & 0 deletions packages/components-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
} from './heading/Heading';
export { LuxHeadingGroup, type LuxHeadingGroupProps } from './heading-group/HeadingGroup';
export { LuxFormField, type LuxFormFieldProps } from './form-field/FormField';
export { LuxFormFieldCheckbox, type LuxFormFieldCheckboxProps } from './form-field-checkbox/FormFieldCheckbox';
export {
LuxFormFieldDescription,
type LuxFormFieldDescriptionProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Canvas, Controls, Meta } from "@storybook/blocks";
import * as FormFieldCheckboxStories from "./form-field-checkbox.stories";

<Meta of={FormFieldCheckboxStories} />

# Form Field Checkbox

## Playground

<Canvas of={FormFieldCheckboxStories.Playground} />
<Controls of={FormFieldCheckboxStories.Playground} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { LuxFormFieldCheckbox, type LuxFormFieldCheckboxProps } from '@lux-design-system/components-react';
import tokens from '@lux-design-system/design-tokens/dist/index.json';
import type { Meta, StoryObj } from '@storybook/react';
import { BADGES } from '../../../config/preview';
import CheckboxMeta from '../checkbox/checkbox.stories';
import FormFieldDescriptionMeta from '../form-field-description/form-field-description.stories';
import FormFieldErrorMessageMeta from '../form-field-error-message/form-field-error-message.stories';

const meta = {
title: 'React Components/Form Field/Form Field Checkbox',
id: 'react-components-form-field-form-field-checkbox',
component: LuxFormFieldCheckbox,
parameters: {
badges: [BADGES.WIP, BADGES.CANARY],
tokens,
tokensPrefix: 'utrecht-form-field-checkbox',
},
argTypes: {
...CheckboxMeta.argTypes,
appearance: {
...FormFieldDescriptionMeta.argTypes.appearance,
},
distanced: {
...FormFieldErrorMessageMeta.argTypes.distanced,
},
disabled: {
type: 'boolean',
},
errorMessage: {
if: {
arg: 'invalid',
truthy: true,
},
},
},
} satisfies Meta<LuxFormFieldCheckboxProps>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Playground: Story = {
name: 'Playground',
args: {
label: 'Label',
description: 'Description',
errorMessage: 'ErrorMessage',
invalid: false,
appearance: undefined,
},
parameters: {
docs: {
sourceState: 'shown',
},
},
tags: ['!autodocs'],
};

export const Invalid: Story = {
name: 'Invalid',
args: {
...Playground.args,
invalid: true,
},
};

export const Disabled: Story = {
name: 'Disabled',
args: {
...Playground.args,
disabled: true,
},
};

0 comments on commit cf5127d

Please sign in to comment.