Skip to content

Commit

Permalink
Merge branch 'main' into react-component-radio-button
Browse files Browse the repository at this point in the history
  • Loading branch information
MrSkippy authored Nov 20, 2024
2 parents eb93a58 + 3adfa8d commit 254727b
Show file tree
Hide file tree
Showing 24 changed files with 1,044 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/rude-fishes-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lux-design-system/components-react": minor
---

Nieuw component: Checkbox
11 changes: 11 additions & 0 deletions .changeset/ten-avocados-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@lux-design-system/design-tokens": major
---

In deze commit:

- Gewijzigde tokens:
- utrecht heading-1: color van `brand` naar `foreground`, font-weight van `bold` naar `regular`
- utrecht heading-3: color van `brand` naar `foreground`

**Let op:** visuele wijziging op alle thema's, H1 en H3 hebben nu een andere vormgeving.
5 changes: 5 additions & 0 deletions .changeset/weak-weeks-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lux-design-system/components-react": minor
---

Nieuw component: Link
8 changes: 4 additions & 4 deletions .lux.stylelintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
}
],
"order/properties-alphabetical-order": null,
"scss/dollar-variable-pattern": "^(lux)-[a-z0-9-]+$",
"scss/percent-placeholder-pattern": "^(lux)-[a-z0-9-]+$",
"scss/dollar-variable-pattern": "^(lux|utrecht)-[a-z0-9-]+$",
"scss/percent-placeholder-pattern": "^(lux|utrecht)-[a-z0-9-]+$",
"custom-property-pattern": "^_?(lux|utrecht)-[a-z0-9-]+$",
"selector-class-pattern": "^(lux)-[a-z0-9_-]+|(force-state)--[a-z]+$",
"keyframes-name-pattern": "^(lux)-[a-z0-9-]+$"
"selector-class-pattern": "^(lux|utrecht)-[a-z0-9_-]+|(force-state)--[a-z]+$",
"keyframes-name-pattern": "^(lux|utrecht)-[a-z0-9-]+$"
}
}
27 changes: 27 additions & 0 deletions packages/components-react/src/checkbox/Checkbox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.lux-checkbox:checked:focus {
border-color: var(--lux-checkbox-checked-focus-border-color);
background-color: var(--lux-checkbox-checked-focus-background-color);
color: var(--lux-checkbox-checked-focus-color);
}

.lux-checkbox:checked:hover {
border-color: var(--lux-checkbox-checked-hover-border-color);
background-color: var(--lux-checkbox-checked-hover-background-color);
color: var(--lux-checkbox-checked-hover-color);
}

.lux-checkbox:checked:active {
border-color: var(--lux-checkbox-checked-active-border-color);
background-color: var(--lux-checkbox-checked-active-background-color);
color: var(--lux-checkbox-checked-active-color);
}

.lux-checkbox:checked:disabled {
border-color: var(--lux-checkbox-checked-disabled-border-color);
background-color: var(--lux-checkbox-checked-disabled-background-color);
color: var(--lux-checkbox-checked-disabled-color);
}

.lux-checkbox--disabled {
cursor: not-allowed;
}
48 changes: 48 additions & 0 deletions packages/components-react/src/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
Checkbox as UtrechtCheckbox,
type CheckboxProps as UtrechtCheckboxProps,
} from '@utrecht/component-library-react/dist/css-module';
import './Checkbox.css';
import clsx from 'clsx';
import { ForwardedRef, forwardRef, PropsWithChildren } from 'react';

export type LuxCheckboxProps = UtrechtCheckboxProps & {
invalid?: boolean;
name?: string;
checked?: boolean;
disabled?: boolean;
className?: string;
};

const CLASSNAME = {
checkbox: 'lux-checkbox',
disabled: 'lux-checkbox--disabled',
};

export const LuxCheckbox = forwardRef(
(
{ disabled, className, name, checked, ...restProps }: PropsWithChildren<LuxCheckboxProps>,
ref: ForwardedRef<HTMLInputElement>,
) => {
const combinedClassName = clsx(
CLASSNAME.checkbox,
{
[CLASSNAME.disabled]: disabled,
},
className,
);

return (
<UtrechtCheckbox
ref={ref}
name={name}
className={combinedClassName}
checked={checked}
disabled={disabled}
{...restProps}
/>
);
},
);

LuxCheckbox.displayName = 'LuxCheckbox';
66 changes: 66 additions & 0 deletions packages/components-react/src/checkbox/test/Checkbox.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { describe, expect, it } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import { LuxCheckbox } from '../Checkbox';

describe('Checkbox', () => {
it('renders a checkbox', () => {
render(<LuxCheckbox name="test-checkbox" />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeInTheDocument();
});

it('renders a checkbox with correct name attribute', () => {
render(<LuxCheckbox name="test-checkbox" />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveAttribute('name', 'test-checkbox');
});

it('renders a checked checkbox when checked prop is true', () => {
render(<LuxCheckbox name="test-checkbox" checked={true} />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeChecked();
});

it('renders an unchecked checkbox when checked prop is false', () => {
render(<LuxCheckbox name="test-checkbox" checked={false} />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).not.toBeChecked();
});

it('renders a disabled checkbox when disabled prop is true', () => {
render(<LuxCheckbox name="test-checkbox" disabled={true} />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeDisabled();
expect(checkbox).toHaveClass('lux-checkbox--disabled');
});

it('applies custom className when provided', () => {
const customClass = 'custom-checkbox';
render(<LuxCheckbox name="test-checkbox" className={customClass} />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveClass(customClass);
});

it('forwards additional props to the checkbox input', () => {
render(<LuxCheckbox name="test-checkbox" data-testid="test-id" aria-label="test label" />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveAttribute('data-testid', 'test-id');
expect(checkbox).toHaveAttribute('aria-label', 'test label');
});

it('combines multiple classes correctly', () => {
render(<LuxCheckbox name="test-checkbox" className="custom-class" disabled={true} />);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveClass('lux-checkbox');
expect(checkbox).toHaveClass('lux-checkbox--disabled');
expect(checkbox).toHaveClass('custom-class');
});
});
2 changes: 2 additions & 0 deletions packages/components-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
LuxFormFieldErrorMessage,
type LuxFormFieldErrorMessageProps,
} from './form-field-error-message/FormFieldErrorMessage';
export { LuxLink, type LuxLinkProps } from './link/Link';
export { LuxTextbox, INPUT_TYPES, type LuxTextboxProps } from './textbox/Textbox';
export { LuxFormFieldTextbox, type LuxFormFieldTextboxProps } from './form-field-textbox/FormFieldTextbox';
export { LuxParagraph, type LuxParagraphProps } from './paragraph/Paragraph';
Expand All @@ -30,5 +31,6 @@ export {
type LuxFormFieldRadioOptionProps,
} from './form-field-radio-option/FormFieldRadioOption';
export { LuxFormFieldRadioGroup, type LuxFormFieldRadioGroupProps } from './form-field-radio-group/FormFieldRadioGroup';
export { LuxCheckbox, type LuxCheckboxProps } from './checkbox/Checkbox';
export { LuxPreHeading, type LuxPreHeadingProps } from './pre-heading/PreHeading';
export { LuxSection, type LuxSectionProps } from './section/Section';
20 changes: 20 additions & 0 deletions packages/components-react/src/link/Link.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.utrecht-link--html-span:active,
.utrecht-link--html-a:any-link:active,
.utrecht-link--active,
.utrecht-link--visited {
--_utrecht-link-state-text-decoration-color: var(--utrecht-link-active-color);
}

.lux-link {
display: inline flex;
align-items: baseline;
gap: var(--lux-link-column-gap);
}

.lux-link-icon--start {
order: 0;
}

.lux-link-icon--end {
order: 1;
}
64 changes: 64 additions & 0 deletions packages/components-react/src/link/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
Link as UtrechtLink,
type LinkProps as UtrechtLinkProps,
} from '@utrecht/component-library-react/dist/css-module';
import clsx from 'clsx';
import React, { ReactElement } from 'react';
import './Link.css';

type IconPosition = 'start' | 'end';

export type LuxLinkProps = UtrechtLinkProps & {
external?: boolean;
icon?: ReactElement | undefined;
iconPosition?: IconPosition;
};

const CLASSNAMES = {
link: 'lux-link',
text: 'lux-link__text',
};

const ICON_POSITIONS: { [key: string]: string } = {
start: 'lux-link-icon--start',
end: 'lux-link-icon--end',
};

export const LuxLink = (props: LuxLinkProps) => {
const {
external = false,
className,
children,
icon: iconNode,
iconPosition: providedIconPosition,
...otherProps
} = props;

// Set default icon position to 'start' if there's an icon but no position specified
const iconPosition = iconNode ? providedIconPosition || 'start' : undefined;

const combinedClassName = clsx(CLASSNAMES.link, className);

const positionedIcon = React.Children.map(iconNode, (iconElement) => {
if (!iconElement) {
return null;
}

if (!React.isValidElement<HTMLElement>(iconElement)) {
return iconElement;
}

return React.cloneElement(iconElement as ReactElement, {
className: clsx(iconElement?.props?.className, iconPosition && ICON_POSITIONS[iconPosition]),
});
});

const externalProps = external ? { rel: 'external noopener noreferrer' } : {};

return (
<UtrechtLink className={combinedClassName} {...externalProps} {...otherProps}>
{positionedIcon}
<span className={CLASSNAMES.text}>{children}</span>
</UtrechtLink>
);
};
Loading

0 comments on commit 254727b

Please sign in to comment.