Skip to content

Commit

Permalink
feat: create FormLabel react component package
Browse files Browse the repository at this point in the history
  • Loading branch information
AliKdhim87 authored and Robbert committed Oct 21, 2024
1 parent af681a3 commit dda41cb
Show file tree
Hide file tree
Showing 13 changed files with 505 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-boxes-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@utrecht/form-label-react": major
---

Create FormLabel react component package
8 changes: 8 additions & 0 deletions packages/components-react/form-label-react/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"env": {
"jest/globals": true
},
"rules": {
"react/react-in-jsx-scope": "off"
}
}
12 changes: 12 additions & 0 deletions packages/components-react/form-label-react/babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
presets: [
'@babel/preset-env',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
'@babel/preset-typescript',
],
};
8 changes: 8 additions & 0 deletions packages/components-react/form-label-react/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['/dist/'],
moduleNameMapper: {
'^@utrecht/(.*)$': '<rootDir>/../$1/src/',
},
};
68 changes: 68 additions & 0 deletions packages/components-react/form-label-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@utrecht/form-label-react",
"version": "0.0.0",
"author": "Community for NL Design System",
"description": "Form Label component for the Municipality of Utrecht based on the NL Design System architecture",
"license": "EUPL-1.2",
"main": "./dist/index.cjs.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"files": [
"dist/",
"src/"
],
"sideEffects": false,
"scripts": {
"clean": "rimraf dist *.tsbuildinfo .rollup.cache coverage",
"build": "rollup --config ./rollup.config.mjs",
"test": "mkdir -p pages && jest --coverage --verbose",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@babel/plugin-transform-runtime": "7.24.7",
"@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-typescript": "12.1.0",
"@testing-library/dom": "8.20.1",
"@testing-library/jest-dom": "6.5.0",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.1",
"@types/jest": "29.5.13",
"@types/react": "18.3.3",
"@types/testing-library__jest-dom": "5.14.9",
"@utrecht/form-label-css": "workspace:*",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"react": "18.3.1",
"rollup": "4.23.0",
"rollup-plugin-filesize": "10.0.0",
"rollup-plugin-node-externals": "7.1.2",
"rollup-plugin-peer-deps-external": "2.2.4",
"rollup-plugin-postcss": "4.0.2",
"typescript": "5.6.2"
},
"keywords": [
"nl-design-system"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git+ssh",
"url": "git@github.com:nl-design-system/utrecht.git",
"directory": "packages/components-react/form-label-react"
},
"peerDependencies": {
"@babel/runtime": "*",
"react": "18",
"react-dom": "18"
},
"dependencies": {
"clsx": "2.1.1"
}
}
87 changes: 87 additions & 0 deletions packages/components-react/form-label-react/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import { readFileSync } from 'fs';
import filesize from 'rollup-plugin-filesize';
import nodeExternal from 'rollup-plugin-node-externals';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';

const packagePath = new URL('./package.json', import.meta.url).pathname;
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));

/**
* @type {import('rollup').RollupOptions}
*/
export default [
{
input: './src/index.tsx',
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true,
},
{
file: packageJson.module,
format: 'es',
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
commonjs(),
nodeExternal(),
resolve({ browser: true }),
json(),
typescript({ tsconfig: './tsconfig.build.json' }),
babel({
presets: ['@babel/preset-react'],
babelHelpers: 'runtime',
exclude: ['node_modules/**', 'dist/**'],
extensions: ['.ts', '.tsx'],
inputSourceMap: true,
plugins: ['@babel/plugin-transform-runtime'],
}),
filesize(),
],
},
{
input: './src/css.tsx',
output: [
{
file: './dist/css.js',
format: 'cjs',
sourcemap: true,
},
{
file: './dist/css.mjs',
format: 'es',
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
commonjs(),
nodeExternal(),
resolve({ browser: true }),
json(),
postcss({
extensions: ['.css', '.scss'],
minimize: true,
}),
typescript({ tsconfig: './tsconfig.build.json' }),
babel({
presets: ['@babel/preset-react'],
babelHelpers: 'runtime',
exclude: ['node_modules/**', 'dist/**'],
extensions: ['.ts', '.tsx'],
inputSourceMap: true,
plugins: ['@babel/plugin-transform-runtime'],
}),
filesize(),
],
},
];
9 changes: 9 additions & 0 deletions packages/components-react/form-label-react/src/css.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @license EUPL-1.2
* Copyright (c) 2020-2025 Frameless B.V.
* Copyright (c) 2021-2025 Gemeente Utrecht
*/

import '@utrecht/form-label-css/src/index.scss';

export * from './index';
147 changes: 147 additions & 0 deletions packages/components-react/form-label-react/src/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { render, screen } from '@testing-library/react';
import { createRef } from 'react';
import { FormLabel } from './index';
import '@testing-library/jest-dom';

describe('Form label', () => {
it('renders an HTML label element', () => {
const { container } = render(<FormLabel htmlFor="form-control" />);

const label = container.querySelector('label:only-child');

expect(label).toBeInTheDocument();
expect(label).toBeVisible();
});

it('renders an HTML label element with for attribute', () => {
const { container } = render(<FormLabel htmlFor="form-control" />);

const label = container.querySelector('label[for="form-control"]:only-child');

expect(label).toBeInTheDocument();
});

it('renders a design system BEM class name', () => {
const { container } = render(<FormLabel htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('utrecht-form-label');
});

describe('variant for radio button in radio group', () => {
it('renders a design system BEM class name', () => {
const { container } = render(<FormLabel htmlFor="form-control" type="radio" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('utrecht-form-label--radio');
});
});

describe('variant for checkbox in checkbox group', () => {
it('renders a design system BEM class name', () => {
const { container } = render(<FormLabel htmlFor="form-control" type="checkbox" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('utrecht-form-label--checkbox');
});
});

describe('variant for a disabled form control', () => {
it('renders a design system BEM class name', () => {
const { container } = render(<FormLabel disabled htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('utrecht-form-label--disabled');
});
});

describe('variant for a selected checkbox or radio button', () => {
it('renders a design system BEM class name', () => {
const { container } = render(<FormLabel checked htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('utrecht-form-label--checked');
});
});

// Skipped because in js-dom the element unexpectedly does not have an associated `display` style
it.skip('displays as CSS inline element', () => {
const { container } = render(<FormLabel htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toBeVisible();
expect(label).toHaveStyle({ display: 'inline' });
});

it('renders rich text content', () => {
const { container } = render(
<FormLabel htmlFor="form-control">
<strong>Current</strong> password
</FormLabel>,
);

const label = container.querySelector(':only-child');

const richText = label?.querySelector('strong');

expect(richText).toBeInTheDocument();
});

it('can be associated with an HTML form input', () => {
render(
<>
<FormLabel htmlFor="email">Email</FormLabel>
<input type="email" id="email" />
</>,
);

const textbox = screen.getByRole('textbox', {
name: 'Email',
});

expect(textbox).toBeInTheDocument();
expect(textbox).toBeVisible();
});

it('can be hidden', () => {
const { container } = render(<FormLabel hidden htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).not.toBeVisible();
});

it('can have a custom class name', () => {
const { container } = render(<FormLabel className="question" htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('question');
});

it('can have a additional class name', () => {
const { container } = render(<FormLabel className="large" htmlFor="form-control" />);

const label = container.querySelector(':only-child');

expect(label).toHaveClass('large');

expect(label).toHaveClass('utrecht-form-label');
});

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLLabelElement>();

const { container } = render(<FormLabel htmlFor="form-control" ref={ref} />);

const div = container.querySelector(':only-child');

expect(ref.current).toBe(div);
});
});
37 changes: 37 additions & 0 deletions packages/components-react/form-label-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license EUPL-1.2
* Copyright (c) 2020-2025 Frameless B.V.
* Copyright (c) 2021-2025 Gemeente Utrecht
*/

import clsx from 'clsx';
import { ForwardedRef, forwardRef, LabelHTMLAttributes, PropsWithChildren } from 'react';

export interface FormLabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
type?: 'checkbox' | 'radio';
disabled?: boolean;
checked?: boolean;
}

export const FormLabel = forwardRef(
(
{ children, className, type, disabled, checked, ...restProps }: PropsWithChildren<FormLabelProps>,
ref: ForwardedRef<HTMLLabelElement>,
) => (
<label
{...restProps}
ref={ref}
className={clsx(
'utrecht-form-label',
type && `utrecht-form-label--${type}`,
disabled && 'utrecht-form-label--disabled',
checked && 'utrecht-form-label--checked',
className,
)}
>
{children}
</label>
),
);

FormLabel.displayName = 'FormLabel';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"composite": true
}
}
Loading

0 comments on commit dda41cb

Please sign in to comment.