Skip to content
Open
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
4 changes: 4 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
max-width: 1200px;
}

/* When a story is taller than a sized viewport, the element the backgrounds addon colours
(.sb-show-main) can be shorter than the content, exposing the iframe's default grey behind it.
Painting <html> makes the surface propagate across the whole scroll area; !important beats
Storybook's own preview body background. The token resolves per the theme class on <html>. */
html.tedi-theme--default,
html.tedi-theme--dark {
background-color: var(--general-surface-primary) !important;
Expand Down
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
"i18n-ally.localesPaths": [],
"i18n-ally.keystyle": "nested",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.format.enable": true,
"eslint.alwaysShowStatus": true,
"eslint.workingDirectories": [
Expand Down
29 changes: 26 additions & 3 deletions component.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,9 @@
"name": "TableOfContents",
"category": "navigation",
"sourcePath": "src/tedi/components/navigation/table-of-contents",
"status": [],
"status": [
"breakpointSupport"
],
"description": "Navigational table of contents for long pages or multistep forms, composed from nestable TableOfContents.Item entries.",
"keyProps": [
"activeId",
Expand Down Expand Up @@ -744,7 +746,9 @@
"name": "Radio",
"category": "form",
"sourcePath": "src/tedi/components/form/radio",
"status": [],
"status": [
"breakpointSupport"
],
"description": "Single-select radio input with a label and helper text; supports a selectable card variant and grouping via Radio.Group.",
"keyProps": [
"id",
Expand Down Expand Up @@ -852,7 +856,9 @@
"name": "Checkbox",
"category": "form",
"sourcePath": "src/tedi/components/form/checkbox",
"status": [],
"status": [
"breakpointSupport"
],
"description": "Checkbox input with a label and helper text; supports an indeterminate state, a selectable card variant, and grouping via Checkbox.Group.",
"keyProps": [
"id",
Expand Down Expand Up @@ -1157,6 +1163,23 @@
"indicatorSemantics"
]
},
{
"id": "form/rating/rating",
"name": "Rating",
"category": "form",
"sourcePath": "src/tedi/components/form/rating/rating",
"status": [
"breakpointSupport"
],
"description": "Accessible rating scale (native radiogroup) with star, number, or icon fill; supports controlled/uncontrolled, a read-only summary/scale variant, and breakpoint-aware orientation.",
"keyProps": [
"label",
"type",
"value",
"onChange",
"count"
]
},
{
"id": "layout/sidenav",
"name": "SideNav",
Expand Down
2 changes: 1 addition & 1 deletion skills/tedi-react/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ const [email, setEmail] = useState('');
</Radio.Group>
```

Form controls: `TextField`, `Select`, `Textarea`, `NumberField`, `Checkbox` (+ `Checkbox.Group`), `Radio` (+ `Radio.Group`), `Search`, `DateField`, `TimeField`, `Filter` (+ `FilterGroup`), `FileUpload`, `FileDropzone`. `ChoiceGroup` is **deprecated** — use `Radio.Group` / `Checkbox.Group`. Verify the roster and per-control usage against [references/forms.md](references/forms.md) and the installed package (see Authoritative Sources).
Form controls: `TextField`, `Select`, `Textarea`, `NumberField`, `Checkbox` (+ `Checkbox.Group`), `Radio` (+ `Radio.Group`), `Search`, `Rating`, `DateField`, `TimeField`, `Filter` (+ `FilterGroup`), `FileUpload`, `FileDropzone`. `ChoiceGroup` is **deprecated** — use `Radio.Group` / `Checkbox.Group`. Verify the roster and per-control usage against [references/forms.md](references/forms.md) and the installed package (see Authoritative Sources).

## Theming

Expand Down
7 changes: 7 additions & 0 deletions skills/tedi-react/references/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ files. This is the part of this document worth maintaining by hand.
- **`TableCard` is `Table`'s readable mobile form.** Below a breakpoint, render a list of
`TableCard` (each row a stacked `<dl>` of label / value pairs with its own title / status /
actions) instead of collapsing columns; swap via `useBreakpoint` + `isBreakpointBelow(bp, 'md')`.
- **`Rating` is a native `radiogroup`, not a decorative widget.** It renders real radios, so it's
keyboard-navigable (arrow keys) and form-submittable like any radio group. `value` is **1-based**
(`0` = no rating), and `type` changes the fill: `star` / `number` fill cumulatively up to the
value, while `icon` highlights only the single selected item (defaults to the five sentiment
faces at `count={5}`). Pass `readOnly` for a compact aggregate summary (or `readOnlyVariant="scale"`
for a fractional star scale with `ratingsCount`), and `orientation` is breakpoint-aware
(`orientation="vertical" md={{ orientation: 'horizontal' }}`) for narrow layouts.

### Composition constraints

Expand Down
2 changes: 2 additions & 0 deletions skills/tedi-react/references/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Orientation only. Verify the current roster against the installed package's barr
| Radio | `boolean` (via onChange) | `Radio.Group`, card variant, segmented layout |
| ChoiceGroup | `ChoiceGroupValue` | **Deprecated** — use `Radio.Group` / `Checkbox.Group` |
| Search | `string` | Search button, onSearch callback |
| Rating | `number` (1-based; `0` = none) | Star / number / icon scale, native `radiogroup`, `readOnly` summary/scale, breakpoint-aware `orientation`, `itemLabels` |
| DateField | `Date \| Date[] \| DateRange` | Single/multiple/range, manual input, min/max, native picker, clearable, breakpoint-aware |
| TimeField | `string` (`"HH:mm"`) | Wheel / grid picker, native fallback, stepMinutes, availableTimes, clearable |
| Filter | `boolean \| string \| string[]` | Pill-shaped toggle / dropdown filter — single, multi-select, custom panel; pairs with `FilterGroup` |
Expand Down Expand Up @@ -456,6 +457,7 @@ TEDI form controls hand you the **parsed value**, not the raw DOM event. The con
- **Select** calls `onChange` with the selected option object(s), or `null` when cleared.
- **DateField** uses `onSelect`; the value shape follows the active `mode` (single `Date`, `Date[]`, or a range).
- **TimeField / TimePicker** call `onChange` with a `"HH:mm"` 24-hour string (empty when cleared).
- **Rating** calls `onChange` with the selected 1-based number (`0` means no rating).

## Disabled State

Expand Down
1 change: 1 addition & 0 deletions src/tedi/components/form/rating/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './rating';
232 changes: 232 additions & 0 deletions src/tedi/components/form/rating/rating.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
@use '@tedi-design-system/core/bootstrap-utility/breakpoints';

.tedi-rating {
display: flex;
flex-direction: column;
gap: var(--layout-grid-gutters-04);
align-items: flex-start;
width: fit-content;
}

.tedi-rating__items {
display: flex;
gap: var(--feedback-star-inner-spacing);
align-items: flex-start;
}

.tedi-rating--number .tedi-rating__items,
.tedi-rating--icon .tedi-rating__items {
flex-wrap: wrap;
gap: var(--layout-grid-gutters-08);
}

.tedi-rating__item {
position: relative;
display: inline-flex;
flex-direction: column;
gap: var(--layout-grid-gutters-04);
align-items: center;
cursor: pointer;
}

.tedi-rating--disabled .tedi-rating__item {
cursor: not-allowed;
}

.tedi-rating--vertical .tedi-rating__items {
flex-direction: column;
align-items: stretch;
}

.tedi-rating--vertical .tedi-rating__item {
flex-direction: row;
gap: var(--layout-grid-gutters-08);
justify-content: flex-start;
}

.tedi-rating--vertical .tedi-rating__caption {
text-align: left;
}

.tedi-rating__input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
cursor: inherit;
opacity: 0;
}

.tedi-rating__visual {
position: relative;
display: inline-flex;
}

.tedi-rating .tedi-rating__star {
position: relative; // paint above the absolutely positioned backdrop
line-height: 1;
color: var(--general-text-brand);
text-align: center;
}

.tedi-rating--star .tedi-rating__visual {
padding: var(--feedback-star-inner-spacing);
}

// Filled glyph stacked under the outlined one; transparent unless the star is hovered while unselected.
.tedi-rating .tedi-rating__star-backdrop {
position: absolute;
inset: var(--feedback-star-inner-spacing);
color: transparent;
}

.tedi-rating__circle {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--button-sm-height);
height: var(--button-sm-height);
font-size: var(--body-small-regular-size);
color: var(--general-text-brand);
background-color: var(--general-surface-primary);
border: var(--tedi-borders-01) solid var(--general-border-secondary);
border-radius: 50%;
}

.tedi-rating__visual--selected .tedi-rating__circle,
.tedi-rating__circle--filled {
color: var(--general-text-white);
background-color: var(--general-surface-brand-primary);
border-color: var(--general-surface-brand-primary);
}

.tedi-rating:not(.tedi-rating--disabled) {
.tedi-rating__visual--hover,
.tedi-rating__item:hover > .tedi-rating__visual {
.tedi-rating__star {
color: var(--general-icon-brand-dark);
}

.tedi-rating__star-backdrop {
color: var(--general-surface-brand-quaternary);
}

.tedi-rating__circle {
color: var(--general-icon-brand-dark);
background-color: var(--general-surface-brand-quaternary);
border-color: var(--general-icon-brand-dark);
}

&.tedi-rating__visual--selected .tedi-rating__circle {
color: var(--general-text-white);
background-color: var(--general-icon-brand-dark);
border-color: var(--general-icon-brand-dark);
}
}
}

.tedi-rating__input:focus-visible + .tedi-rating__visual::after {
position: absolute;
inset: -3px;
pointer-events: none;
content: '';
border-radius: 50%;
box-shadow: inset 0 0 0 2px var(--general-effect-colors-focus-outset-outer-shadow),
inset 0 0 0 3px var(--general-effect-colors-focus-outset-inner-shadow);
}

.tedi-rating__caption {
font-size: var(--body-extra-small-size);
line-height: var(--body-extra-small-line-height);
color: var(--general-text-secondary);
text-align: center;
}

.tedi-rating__star-caption {
align-self: stretch;
min-height: var(--body-extra-small-line-height);
font-size: var(--body-extra-small-size);
line-height: var(--body-extra-small-line-height);
color: var(--general-text-secondary);
text-align: center;
}

.tedi-rating--number:not(.tedi-rating--vertical) {
.tedi-rating__items--captioned {
row-gap: var(--layout-grid-gutters-24);
padding-bottom: var(--layout-grid-gutters-24);
}

.tedi-rating__caption {
position: absolute;
top: 100%;
left: 50%;
margin-top: var(--layout-grid-gutters-04);
white-space: nowrap;
transform: translateX(-50%);
}
}

.tedi-rating--disabled {
.tedi-rating__star {
color: var(--form-general-border-disabled);
}

.tedi-rating__visual--selected .tedi-rating__star {
color: var(--form-general-background-disabled);
}

.tedi-rating__circle {
color: var(--general-text-disabled);
background-color: transparent;
border-color: var(--form-general-border-disabled);
}

.tedi-rating__visual--selected .tedi-rating__circle {
background-color: var(--form-general-background-disabled);
}
}

.tedi-rating--readonly {
flex-direction: row;
align-items: center;
}

.tedi-rating__summary {
display: inline-flex;
gap: var(--layout-grid-gutters-08);
align-items: center;
}

.tedi-rating__stars {
display: inline-flex;
gap: var(--feedback-star-inner-spacing);
}

.tedi-rating__star-partial {
position: relative;
display: inline-flex;
}

.tedi-rating__star-partial-fill {
position: absolute;
inset-block: 0;
inset-inline-start: 0;
display: inline-flex;
width: var(--tedi-rating-star-fill);
overflow: hidden;
}

.tedi-rating__summary-text {
font-size: var(--heading-h6-size);
font-weight: var(--heading-h6-weight);
line-height: var(--heading-h6-line-height);
color: var(--general-text-secondary);
}

@include breakpoints.media-breakpoint-down(md) {
.tedi-rating--number:not(.tedi-rating--readonly, .tedi-rating--vertical) .tedi-rating__items {
column-gap: var(--layout-grid-gutters-04);
}
}
Loading
Loading