diff --git a/.changeset/tender-chairs-doubt.md b/.changeset/tender-chairs-doubt.md new file mode 100644 index 0000000..a841bab --- /dev/null +++ b/.changeset/tender-chairs-doubt.md @@ -0,0 +1,6 @@ +--- +"@scouterna/ui-react": patch +"@scouterna/ui-webc": patch +--- + +Align all input components diff --git a/.changeset/wicked-books-peel.md b/.changeset/wicked-books-peel.md new file mode 100644 index 0000000..7749924 --- /dev/null +++ b/.changeset/wicked-books-peel.md @@ -0,0 +1,6 @@ +--- +"@scouterna/ui-react": minor +"@scouterna/ui-webc": minor +--- + +Add radio button component diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..799c8ed --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,201 @@ +# Copilot Instructions for j26-components + +## Repository Overview + +This is the **Scouterna Design System Components** monorepo containing: +- **Stencil Web Components** (packages/ui-webc) - source of truth for all components +- **React Component Wrappers** (packages/ui-react) - auto-generated from Stencil +- **Design Tokens** (packages/design-tokens) - CSS variables and theme values +- **Storybook Documentation** (packages/storybook) - component demos and documentation + +**Languages**: TypeScript, CSS, React, Stencil +**Tooling**: Biome (linting/formatting), Changesets (releases), Plop (generators) + +## Critical Build Requirements + +**ALWAYS run commands in this exact order to avoid build failures:** + +### 1. Initial Setup +```bash +pnpm install # Required before ANY other command +``` + +### 2. Full Build (required after changes) +```bash +pnpm build # Builds design-tokens → ui-webc → ui-react in dependency order +``` + +**Build sequence is critical:** +- design-tokens MUST build first (generates CSS variables) +- ui-webc depends on design-tokens being built +- ui-react is auto-generated from ui-webc build output +- Never run individual package builds unless explicitly testing + +### 3. Development Mode +```bash +pnpm dev # Runs all packages in watch mode, starts Storybook on :6006 +``` + +### 4. Linting (CI runs this) +```bash +pnpm lint # Check for errors +pnpm lint:fix # Auto-fix formatting/imports +``` + +**CI Requirement**: Code MUST pass `biome ci --error-on-warnings .` - warnings are treated as errors in CI. + +## Project Architecture & File Locations + +### Component Structure +Each component lives in `packages/ui-webc/src/components/{name}/`: +- `{name}.tsx` - Stencil component (source of truth) +- `{name}.css` - Component styles (uses design tokens) +- `readme.md` - **AUTO-GENERATED** documentation (never edit manually) + +Component naming convention: All components use `scout-{name}` tag name (e.g., `scout-button`) + +### Key Configuration Files +- `/package.json` - Root workspace scripts +- `/pnpm-workspace.yaml` - Monorepo workspace config +- `/biome.json` - Linting and formatting rules +- `/.node-version` - Node version (24.5) +- `/.changeset/config.json` - Release configuration +- `/packages/ui-webc/stencil.config.ts` - Stencil build config with React output target + +### Generated Files (Never Edit Directly) +- `packages/ui-react/lib/components/stencil-generated/**` - Auto-generated from Stencil +- `packages/ui-webc/src/components.d.ts` - Auto-generated type definitions +- `packages/ui-webc/src/components/*/readme.md` - Auto-generated docs +- `packages/design-tokens/dist/**` - Generated from tokens/ JSON files + +### Design Tokens +Source: `packages/design-tokens/tokens/**/*.json` +Output: `packages/design-tokens/dist/tokens.css` (imported in components) +Build warnings about "Unknown CSS Font Shorthand properties" are **expected and safe to ignore**. + +## Component Development Workflow + +### Creating New Components +```bash +pnpm plop # Interactive generator creates all boilerplate +``` + +This creates: +- Component in `packages/ui-webc/src/components/{name}/` +- Storybook story in `packages/storybook/src/stories/{name}.stories.tsx` + +### Modifying Existing Components + +1. Edit source in `packages/ui-webc/src/components/{name}/{name}.tsx` +2. Edit styles in `packages/ui-webc/src/components/{name}/{name}.css` +3. **Rebuild**: `pnpm build` (triggers React wrapper regeneration) +4. Test in Storybook: `pnpm dev` then visit http://localhost:6006 +5. Add changeset: `pnpm changeset` (required for releases) + +**Component Props**: Use JSDoc comments in .tsx file - they auto-generate into readme.md tables. + +### Form Input Components Pattern +Input-like components (checkbox, radio-button, select, input) follow this pattern: +- Must have `name` prop for form submission +- Must have `value` prop +- Must emit `scoutChecked` or `scoutInputChange` events with `{ checked/value, element }` payload +- Must support `disabled` prop +- Must emit internal `_fieldId` event for field association + +## Testing + +### Unit Tests (Stencil) +```bash +cd packages/ui-webc +pnpm test # Runs spec and e2e tests with Stencil's test runner +``` + +Tests are rare in this codebase - only `utils.spec.ts` exists currently. + +### Visual Testing +Use Storybook for manual visual testing. Build and preview with: +```bash +pnpm build # Build all packages first +cd packages/storybook +pnpm dev # Starts on port 6006 +``` + +## Release Process (Changesets) + +**Every user-facing change REQUIRES a changeset:** + +1. After making changes, run: +```bash +pnpm changeset +``` + +2. Select affected packages (usually `@scouterna/ui-webc` and `@scouterna/ui-react`) +3. Choose version bump type (patch/minor/major) +4. Describe changes (appears in changelog) +5. Commit the generated `.changeset/*.md` file with your changes + +**Important**: Regular commits without changesets don't appear in changelogs or trigger releases. + +## CI/CD Pipelines + +### Code Quality Check (runs on all pushes/PRs) +- Workflow: `.github/workflows/code-quality.yml` +- Runs: `biome ci --error-on-warnings .` +- **Warnings = Failure** - must fix before merge + +### Release Process (main branch only) +- Workflow: `.github/workflows/release.yml` +- Triggered: On push to `main` +- Actions: + 1. Runs `pnpm install` + 2. Runs `pnpm build` + 3. Creates release PR with version bumps (if changesets exist) + 4. Publishes to npm when release PR is merged + 5. Triggers Storybook deployment to GitHub Pages + +### Storybook Deployment +- Workflow: `.github/workflows/deploy-github-pages.yml` +- Builds Storybook from `packages/storybook/storybook-static` +- Deploys to GitHub Pages + +**To replicate CI locally:** +```bash +pnpm install +pnpm build +biome ci --error-on-warnings . +``` + +## Common Pitfalls & Workarounds + +1. **"Cannot find module" errors**: Run `pnpm install && pnpm build` - React types won't exist until Stencil builds. + +2. **Storybook shows old component**: After editing a component, MUST run `pnpm build` to regenerate React wrappers before Storybook reflects changes. + +3. **Biome errors about imports**: Run `pnpm lint:fix` - Biome auto-organizes imports. + +4. **Design token warnings**: Warnings about "Unknown CSS Font Shorthand properties" during token build are expected and can be ignored. + +5. **Component readme.md out of sync**: These are auto-generated - edit JSDoc in the .tsx file, then rebuild. + +6. **pnpm workspace resolution failures**: Ensure packages are built in order. Root `pnpm build` handles this automatically. + +## PR Review Guidelines + +### Component Documentation + +**DO NOT** suggest adding comments to the props and events tables in component readme.md files. These tables are auto-generated from the component source code and should remain comment-free to maintain consistency with the generation process. + +Example of tables where comments should NOT be suggested: +- Props tables showing component properties +- Events tables documenting component events + +The documentation for these items should be maintained in the source code itself, not in the generated tables. + +## Trust These Instructions + +These instructions are comprehensive and tested. Only search for additional information if: +- Instructions are incomplete for your specific task +- Instructions are found to be incorrect +- You need to understand implementation details not covered here + +For component changes, always check existing components (e.g., button, checkbox) as reference implementations before searching extensively. diff --git a/packages/storybook/src/stories/radio-button.stories.tsx b/packages/storybook/src/stories/radio-button.stories.tsx new file mode 100644 index 0000000..f9fdeb7 --- /dev/null +++ b/packages/storybook/src/stories/radio-button.stories.tsx @@ -0,0 +1,25 @@ +import { ScoutRadioButton } from "@scouterna/ui-react"; +import preview from "#.storybook/preview"; + +const meta = preview.meta({ + title: "Interaction/Radio Button", + component: ScoutRadioButton, + parameters: { + layout: "centered", + }, +}); + +export default meta; + +export const BasicExample = meta.story({ + args: { + name: "my-radio-group", + }, + render: (args) => ( +
+ + + +
+ ), +}); diff --git a/packages/ui-react/lib/components/stencil-generated/components.ts b/packages/ui-react/lib/components/stencil-generated/components.ts index 19520dc..73d8c96 100644 --- a/packages/ui-react/lib/components/stencil-generated/components.ts +++ b/packages/ui-react/lib/components/stencil-generated/components.ts @@ -7,7 +7,7 @@ /* eslint-disable */ -import { type ScoutCheckboxCustomEvent, type ScoutInputCustomEvent, type ScoutLinkCustomEvent, type ScoutSelectCustomEvent, type ScoutSwitchCustomEvent } from "@scouterna/ui-webc"; +import { type ScoutCheckboxCustomEvent, type ScoutInputCustomEvent, type ScoutLinkCustomEvent, type ScoutRadioButtonCustomEvent, type ScoutSelectCustomEvent, type ScoutSwitchCustomEvent } from "@scouterna/ui-webc"; import { ScoutBottomBarItem as ScoutBottomBarItemElement, defineCustomElement as defineScoutBottomBarItem } from "@scouterna/ui-webc/dist/components/scout-bottom-bar-item.js"; import { ScoutBottomBar as ScoutBottomBarElement, defineCustomElement as defineScoutBottomBar } from "@scouterna/ui-webc/dist/components/scout-bottom-bar.js"; import { ScoutButton as ScoutButtonElement, defineCustomElement as defineScoutButton } from "@scouterna/ui-webc/dist/components/scout-button.js"; @@ -21,6 +21,7 @@ import { ScoutListViewItem as ScoutListViewItemElement, defineCustomElement as d import { ScoutListViewSubheader as ScoutListViewSubheaderElement, defineCustomElement as defineScoutListViewSubheader } from "@scouterna/ui-webc/dist/components/scout-list-view-subheader.js"; import { ScoutListView as ScoutListViewElement, defineCustomElement as defineScoutListView } from "@scouterna/ui-webc/dist/components/scout-list-view.js"; import { ScoutLoader as ScoutLoaderElement, defineCustomElement as defineScoutLoader } from "@scouterna/ui-webc/dist/components/scout-loader.js"; +import { ScoutRadioButton as ScoutRadioButtonElement, defineCustomElement as defineScoutRadioButton } from "@scouterna/ui-webc/dist/components/scout-radio-button.js"; import { ScoutSelect as ScoutSelectElement, defineCustomElement as defineScoutSelect } from "@scouterna/ui-webc/dist/components/scout-select.js"; import { ScoutStack as ScoutStackElement, defineCustomElement as defineScoutStack } from "@scouterna/ui-webc/dist/components/scout-stack.js"; import { ScoutSwitch as ScoutSwitchElement, defineCustomElement as defineScoutSwitch } from "@scouterna/ui-webc/dist/components/scout-switch.js"; @@ -73,7 +74,7 @@ export const ScoutCard: StencilReactComponent }); export type ScoutCheckboxEvents = { - onScoutCheckboxChecked: EventName>, @@ -86,7 +87,7 @@ export const ScoutCheckbox: StencilReactComponent>, + on_fieldId: EventName> +}; + +export const ScoutRadioButton: StencilReactComponent = /*@__PURE__*/ createComponent({ + tagName: 'scout-radio-button', + elementClass: ScoutRadioButtonElement, + // @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project. + react: React, + events: { + onScoutChecked: 'scoutChecked', + on_fieldId: '_fieldId' + } as ScoutRadioButtonEvents, + defineCustomElement: defineScoutRadioButton +}); + export type ScoutSelectEvents = { onScoutInputChange: EventName extends CustomEvent { detail: T; target: HTMLScoutListViewItemElement; } +export interface ScoutRadioButtonCustomEvent extends CustomEvent { + detail: T; + target: HTMLScoutRadioButtonElement; +} export interface ScoutSelectCustomEvent extends CustomEvent { detail: T; target: HTMLScoutSelectElement; @@ -325,7 +350,7 @@ declare global { new (): HTMLScoutCardElement; }; interface HTMLScoutCheckboxElementEventMap { - "scoutCheckboxChecked": { + "scoutChecked": { checked: boolean; element: HTMLInputElement; }; @@ -431,6 +456,27 @@ declare global { prototype: HTMLScoutLoaderElement; new (): HTMLScoutLoaderElement; }; + interface HTMLScoutRadioButtonElementEventMap { + "scoutChecked": { + checked: boolean; + element: HTMLInputElement; + }; + "_fieldId": string; + } + interface HTMLScoutRadioButtonElement extends Components.ScoutRadioButton, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLScoutRadioButtonElement, ev: ScoutRadioButtonCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLScoutRadioButtonElement, ev: ScoutRadioButtonCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLScoutRadioButtonElement: { + prototype: HTMLScoutRadioButtonElement; + new (): HTMLScoutRadioButtonElement; + }; interface HTMLScoutSelectElementEventMap { "scoutInputChange": { value: string; @@ -494,6 +540,7 @@ declare global { "scout-list-view-item": HTMLScoutListViewItemElement; "scout-list-view-subheader": HTMLScoutListViewSubheaderElement; "scout-loader": HTMLScoutLoaderElement; + "scout-radio-button": HTMLScoutRadioButtonElement; "scout-select": HTMLScoutSelectElement; "scout-stack": HTMLScoutStackElement; "scout-switch": HTMLScoutSwitchElement; @@ -577,7 +624,8 @@ declare namespace LocalJSX { */ "disabled"?: boolean; "label"?: string; - "onScoutCheckboxChecked"?: (event: ScoutCheckboxCustomEvent<{ + "name"?: string; + "onScoutChecked"?: (event: ScoutCheckboxCustomEvent<{ checked: boolean; element: HTMLInputElement; }>) => void; @@ -585,6 +633,7 @@ declare namespace LocalJSX { * Internal event used for form field association. */ "on_fieldId"?: (event: ScoutCheckboxCustomEvent) => void; + "value"?: string; } interface ScoutDivider { } @@ -608,6 +657,7 @@ declare namespace LocalJSX { * Input mode hints for devices with dynamic keyboards. */ "inputmode"?: InputMode; + "name"?: string; "onScoutBlur"?: (event: ScoutInputCustomEvent) => void; "onScoutInputChange"?: (event: ScoutInputCustomEvent<{ value: string; @@ -694,12 +744,38 @@ declare namespace LocalJSX { "size"?: "xs" | "sm" | "base" | "lg" | "xl"; "text"?: string; } + interface ScoutRadioButton { + /** + * Use this prop if you need to connect your radio button with another element describing its use, other than the property label. + */ + "ariaLabelledby"?: string; + /** + * @default false + */ + "checked"?: boolean; + /** + * @default false + */ + "disabled"?: boolean; + "label"?: string; + "name"?: string; + "onScoutChecked"?: (event: ScoutRadioButtonCustomEvent<{ + checked: boolean; + element: HTMLInputElement; + }>) => void; + /** + * Internal event used for form field association. + */ + "on_fieldId"?: (event: ScoutRadioButtonCustomEvent) => void; + "value"?: string; + } interface ScoutSelect { /** * Whether the select is disabled. Disabled selects are not editable, excluded from tab order and are not validated. * @default false */ "disabled"?: boolean; + "name"?: string; "onScoutBlur"?: (event: ScoutSelectCustomEvent) => void; "onScoutInputChange"?: (event: ScoutSelectCustomEvent<{ value: string; @@ -769,6 +845,7 @@ declare namespace LocalJSX { "scout-list-view-item": ScoutListViewItem; "scout-list-view-subheader": ScoutListViewSubheader; "scout-loader": ScoutLoader; + "scout-radio-button": ScoutRadioButton; "scout-select": ScoutSelect; "scout-stack": ScoutStack; "scout-switch": ScoutSwitch; @@ -805,6 +882,7 @@ declare module "@stencil/core" { "scout-list-view-item": LocalJSX.ScoutListViewItem & JSXBase.HTMLAttributes; "scout-list-view-subheader": LocalJSX.ScoutListViewSubheader & JSXBase.HTMLAttributes; "scout-loader": LocalJSX.ScoutLoader & JSXBase.HTMLAttributes; + "scout-radio-button": LocalJSX.ScoutRadioButton & JSXBase.HTMLAttributes; "scout-select": LocalJSX.ScoutSelect & JSXBase.HTMLAttributes; "scout-stack": LocalJSX.ScoutStack & JSXBase.HTMLAttributes; "scout-switch": LocalJSX.ScoutSwitch & JSXBase.HTMLAttributes; diff --git a/packages/ui-webc/src/components/checkbox/checkbox.css b/packages/ui-webc/src/components/checkbox/checkbox.css index 9c73aaf..6580c6b 100644 --- a/packages/ui-webc/src/components/checkbox/checkbox.css +++ b/packages/ui-webc/src/components/checkbox/checkbox.css @@ -24,7 +24,7 @@ .checkbox:checked:hover { background-color: var(--color-background-brand-hovered); - border: 2px solid var(--color-background-brand-hovered); + border-color: var(--color-background-brand-hovered); box-shadow: none; } @@ -67,12 +67,8 @@ label { display: flex; - flex-direction: row-reverse; align-items: center; + gap: var(--spacing-2); font: var(--type-label-base); color: var(--color-text-base); } - -.inlineDivider { - width: var(--spacing-2); -} diff --git a/packages/ui-webc/src/components/checkbox/checkbox.tsx b/packages/ui-webc/src/components/checkbox/checkbox.tsx index cb02a3d..fd956ce 100644 --- a/packages/ui-webc/src/components/checkbox/checkbox.tsx +++ b/packages/ui-webc/src/components/checkbox/checkbox.tsx @@ -25,9 +25,13 @@ export class ScoutCheckbox { @Prop() label: string; + @Prop() value: string; + + @Prop() name: string; + @State() ariaId: string; - @Event() scoutCheckboxChecked: EventEmitter<{ + @Event() scoutChecked: EventEmitter<{ checked: boolean; element: HTMLInputElement; }>; @@ -41,38 +45,33 @@ export class ScoutCheckbox { this._fieldId.emit(this.ariaId); } - onClick(event: Event) { + onChange(event: Event) { const checkbox = event.target as HTMLInputElement; - console.log("checkbox", checkbox.checked); - this.scoutCheckboxChecked.emit({ + this.scoutChecked.emit({ checked: checkbox.checked, element: checkbox, }); } - /* - todo: - - Wrap checkbox with label if used. - - make sure it works with field nicely with label. - */ render() { const Tag = this.label?.length ? "label" : "div"; return ( - {this.label} - this.onClick(event)} style={{ "--icon-checkbox": `url(${checkIcon})` }} - type="checkbox" - id={this.ariaId} aria-labelledby={this.ariaLabelledby} aria-disabled={this.disabled} disabled={this.disabled} checked={this.checked} + onChange={(event) => this.onChange(event)} /> + {this.label} ); } diff --git a/packages/ui-webc/src/components/checkbox/readme.md b/packages/ui-webc/src/components/checkbox/readme.md index 15fab21..b8f5f79 100644 --- a/packages/ui-webc/src/components/checkbox/readme.md +++ b/packages/ui-webc/src/components/checkbox/readme.md @@ -11,14 +11,16 @@ | `checked` | `checked` | | `boolean` | `false` | | `disabled` | `disabled` | | `boolean` | `false` | | `label` | `label` | | `string` | `undefined` | +| `name` | `name` | | `string` | `undefined` | +| `value` | `value` | | `string` | `undefined` | ## Events -| Event | Description | Type | -| ---------------------- | ----------------------------------------------- | --------------------------------------------------------------- | -| `_fieldId` | Internal event used for form field association. | `CustomEvent` | -| `scoutCheckboxChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` | +| Event | Description | Type | +| -------------- | ----------------------------------------------- | --------------------------------------------------------------- | +| `_fieldId` | Internal event used for form field association. | `CustomEvent` | +| `scoutChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` | ---------------------------------------------- diff --git a/packages/ui-webc/src/components/input/input.tsx b/packages/ui-webc/src/components/input/input.tsx index 1e648ab..9df7d3c 100644 --- a/packages/ui-webc/src/components/input/input.tsx +++ b/packages/ui-webc/src/components/input/input.tsx @@ -58,6 +58,8 @@ export class ScoutInput implements ComponentInterface { */ @Prop() value: string = ""; + @Prop() name: string; + /** * Whether the input is disabled. Disabled inputs are not editable, excluded * from tab order and are not validated. @@ -108,6 +110,7 @@ export class ScoutInput implements ComponentInterface { string` | `undefined` | diff --git a/packages/ui-webc/src/components/radio-button/radio-button.css b/packages/ui-webc/src/components/radio-button/radio-button.css new file mode 100644 index 0000000..6af96e4 --- /dev/null +++ b/packages/ui-webc/src/components/radio-button/radio-button.css @@ -0,0 +1,72 @@ +.radio { + width: var(--spacing-6); + height: var(--spacing-6); + appearance: none; + -webkit-appearance: none; + display: flex; + align-content: center; + justify-content: center; + border-radius: 100%; + background-color: var(--color-white); + border: 2px solid var(--color-gray-300); + position: relative; +} + +.radio:hover { + border: 2px solid var(--color-gray-400); + box-shadow: inset 0px 0px 5px 5px var(--color-background-brand-subtle-hovered); + cursor: pointer; +} + +.radio:active { + background-color: var(--color-background-brand-subtle-pressed); +} + +.radio:checked:hover { + border-color: var(--color-background-brand-hovered); + box-shadow: none; +} + +.radio:checked:hover::before { + background-color: var(--color-background-brand-hovered); +} + +.radio:checked { + border-color: var(--color-background-brand-base); +} + +.radio::after { + content: ""; + position: absolute; + width: var(--spacing-10); + height: var(--spacing-10); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.radio:checked::before { + content: ""; + background-color: var(--color-background-brand-base); + width: var(--spacing-4); + height: var(--spacing-4); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 100%; +} + +.radio:disabled { + pointer-events: none; + background-color: var(--color-gray-100); + border-color: var(--color-gray-100); +} + +label { + display: flex; + align-items: center; + gap: var(--spacing-2); + font: var(--type-label-base); + color: var(--color-text-base); +} diff --git a/packages/ui-webc/src/components/radio-button/radio-button.tsx b/packages/ui-webc/src/components/radio-button/radio-button.tsx new file mode 100644 index 0000000..474d56b --- /dev/null +++ b/packages/ui-webc/src/components/radio-button/radio-button.tsx @@ -0,0 +1,76 @@ +import { + Component, + Event, + type EventEmitter, + h, + Prop, + State, +} from "@stencil/core"; + +@Component({ + tag: "scout-radio-button", + styleUrl: "radio-button.css", + scoped: true, +}) +export class ScoutRadioButton { + @Prop() checked: boolean = false; + + @Prop() disabled: boolean = false; + + /** + * Use this prop if you need to connect your radio button with another element describing its use, other than the property label. + */ + @Prop() ariaLabelledby: string; + + @Prop() label: string; + + @Prop() value: string; + + @Prop() name: string; + + @State() ariaId: string; + + @Event() scoutChecked: EventEmitter<{ + checked: boolean; + element: HTMLInputElement; + }>; + /** + * Internal event used for form field association. + */ + @Event() _fieldId: EventEmitter; + + componentWillLoad(): Promise | void { + this.ariaId = `_${Math.random().toString(36).substring(2, 9)}`; + this._fieldId.emit(this.ariaId); + } + + onChange(event: Event) { + const radio = event.target as HTMLInputElement; + + this.scoutChecked.emit({ + checked: radio.checked, + element: radio, + }); + } + + render() { + const Tag = this.label?.length ? "label" : "div"; + return ( + + this.onChange(event)} + /> + {this.label} + + ); + } +} diff --git a/packages/ui-webc/src/components/radio-button/readme.md b/packages/ui-webc/src/components/radio-button/readme.md new file mode 100644 index 0000000..974a26f --- /dev/null +++ b/packages/ui-webc/src/components/radio-button/readme.md @@ -0,0 +1,28 @@ +# scout-radio-button + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | +| `ariaLabelledby` | `aria-labelledby` | Use this prop if you need to connect your radio button with another element describing its use, other than the property label. | `string` | `undefined` | +| `checked` | `checked` | | `boolean` | `false` | +| `disabled` | `disabled` | | `boolean` | `false` | +| `label` | `label` | | `string` | `undefined` | +| `name` | `name` | | `string` | `undefined` | +| `value` | `value` | | `string` | `undefined` | + + +## Events + +| Event | Description | Type | +| -------------- | ----------------------------------------------- | --------------------------------------------------------------- | +| `_fieldId` | Internal event used for form field association. | `CustomEvent` | +| `scoutChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/ui-webc/src/components/select/readme.md b/packages/ui-webc/src/components/select/readme.md index 162c180..7e603b4 100644 --- a/packages/ui-webc/src/components/select/readme.md +++ b/packages/ui-webc/src/components/select/readme.md @@ -33,6 +33,7 @@ A styled native select component for choosing from a list of options. | Property | Attribute | Description | Type | Default | | ---------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----------- | | `disabled` | `disabled` | Whether the select is disabled. Disabled selects are not editable, excluded from tab order and are not validated. | `boolean` | `false` | +| `name` | `name` | | `string` | `undefined` | | `validate` | -- | Custom validation function run on top of the implicit validation performed by the browser. Return a string with the validation message to mark the select as invalid, or null to mark it as valid. | `(value: string) => string` | `undefined` | | `value` | `value` | Value of the select element, in case you want to control it yourself. | `string` | `""` | diff --git a/packages/ui-webc/src/components/select/select.tsx b/packages/ui-webc/src/components/select/select.tsx index 40ece1d..58f1f92 100644 --- a/packages/ui-webc/src/components/select/select.tsx +++ b/packages/ui-webc/src/components/select/select.tsx @@ -26,6 +26,8 @@ export class ScoutSelect implements ComponentInterface { */ @Prop() disabled: boolean = false; + @Prop() name: string; + /** * Custom validation function run on top of the implicit validation performed * by the browser. Return a string with the validation message to mark the @@ -70,6 +72,7 @@ export class ScoutSelect implements ComponentInterface {