Skip to content
Open
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@mui/material": "^5.15.13",
"@mui/x-date-pickers": "^5.0.20",
"@tanstack/react-table": "^8.13.2",
"@tedi-design-system/core": "6.0.1",
"@tedi-design-system/core": "6.2.1",
"classnames": "^2.5.1",
"draft-js": "^0.11.7",
"draftjs-md-converter": "^1.5.2",
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 @@ -154,7 +154,7 @@ const [email, setEmail] = useState('');
<Checkbox id="agree" label="I agree" value="agree" onChange={(val, checked) => setAgreed(checked)} />
```

Form controls: `TextField`, `Select`, `TextArea`, `NumberField`, `Checkbox`, `Radio`, `ChoiceGroup`, `Search`, `DateField`, `FileUpload`, `FileDropzone`.
Form controls: `TextField`, `Select`, `TextArea`, `NumberField`, `Checkbox`, `Radio`, `ChoiceGroup`, `Search`, `DateField`, `Filter` (+ `FilterGroup`), `FileUpload`, `FileDropzone`.

## Theming

Expand Down
78 changes: 78 additions & 0 deletions skills/tedi-react/references/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,84 @@ The ref shape mirrors TextField (`{ input, wrapper }`). In `'multiple'` mode the
<DateField id="dob" label="Date of birth" useNativePicker md={{ useNativePicker: false }} />
```

### Filter / FilterGroup
**Props:** `FilterProps`, `FilterGroupProps` | form

Compact pill-shaped trigger used to refine result sets. Four modes — chosen at render time
by which props are present:

- **Toggle** — no `options`, no `children`. Acts like a sticky checkbox.
- **Single-select dropdown** — pass `options`. Selecting commits the value and closes the panel.
- **Multi-select dropdown** — `options` + `multiselect`. Clicking does not close. Supports
`searchable`, `showSelectAll`, `showClear`.
- **Custom dropdown content** — pass `children` to embed any panel (date picker, radio group).

```tsx
import { Filter, FilterGroup } from '@tedi-design-system/react/tedi';

// Toggle
<Filter text="Active" selected={active} onSelectedChange={setActive} />

// Single-select with "Label: Value" trigger
<Filter
text="Service"
options={[{ label: 'Optometrist', value: '1' }, { label: 'Dentist', value: '2' }]}
preserveLabel
selectedValue={service}
onSelectedValueChange={setService}
showClear
appendTo="body"
/>

// Multi-select with search & "select all"
<Filter
text="Hospitals"
multiselect
options={hospitalOptions}
selectedValues={hospitals}
onSelectedValuesChange={setHospitals}
searchable
showSelectAll
showClear
appendTo="body"
/>

// Custom dropdown content — show clear action that resets consumer state
<Filter text={periodLabel} selected={!!period} showClear onClear={() => setPeriod('')}>
<ChoiceGroup id="period" label="Period" inputType="radio" items={periodItems}
value={period} onChange={setPeriod} />
</Filter>
```

Wrap related filters in `FilterGroup` to coordinate selection:

```tsx
// Single-select group (radio-like, role="radiogroup")
<FilterGroup label="Status" value={status} onValueChange={setStatus}>
<Filter text="All" value="all" />
<Filter text="Active" value="active" />
<Filter text="Done" value="done" />
</FilterGroup>

// Multi-select group (checkbox-like, role="group")
<FilterGroup label="Tags" multiselect values={tags} onValuesChange={setTags}>
<Filter text="Urgent" value="urgent" />
<Filter text="Review" value="review" />
</FilterGroup>

// Visual-only group (no managed props — children stay independent)
<FilterGroup>
<Filter text="Foo" defaultSelected />
<Filter text="Bar" />
</FilterGroup>
```

Key props:
- `variant?: 'primary' | 'secondary'`, `size?: 'default' | 'large'`
- `prepend?: ReactNode`, `append?: ReactNode`, `hidePrependWhenSelected?: boolean`
- `appendTo?: 'body' | HTMLElement` — portal target for the dropdown
- `selectAllLabel?: string` (default `'Vali kõik'`), `clearLabel?: string` (default `'Tühjenda valik'`)

### FileUpload
**Props:** `FileUploadProps` | form
- `id: string` (required), `name: string` (required)
Expand Down
72 changes: 72 additions & 0 deletions skills/tedi-react/references/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ TEDI form controls support both **controlled** and **uncontrolled** modes, follo
| ChoiceGroup | `ChoiceGroupValue` | Radio/checkbox groups, segmented variant |
| Search | `string` | Search button, onSearch callback |
| DateField | `Date \| Date[] \| DateRange` | Single/multiple/range, manual input, min/max, native picker, breakpoint-aware |
| Filter | `boolean \| string \| string[]` | Pill-shaped toggle / dropdown filter — single, multi-select, custom panel; pairs with `FilterGroup` |
| FileUpload | `FileUploadFile[]` | Multi-file, validation, loading states |
| FileDropzone | `FileUploadFile[]` | Drag-and-drop |

Expand Down Expand Up @@ -177,6 +178,77 @@ const [date, setDate] = useState<Date>();
/>
```

## Filter

Compact pill-shaped trigger for refining result sets. Renders one of four modes depending on
which props are present: **toggle** (no `options`/`children`), **single-select dropdown**
(`options`), **multi-select dropdown** (`options` + `multiselect`), or **custom dropdown
content** (`children`).

```tsx
import { Filter, FilterGroup } from '@tedi-design-system/react/tedi';

// Toggle
<Filter text="Active" selected={active} onSelectedChange={setActive} />

// Single-select — `preserveLabel` renders "Label: Selected value"
<Filter
text="Service"
options={serviceOptions}
selectedValue={service}
onSelectedValueChange={setService}
preserveLabel
showClear
appendTo="body"
/>

// Multi-select with searchable + select all + clear
<Filter
text="Hospitals"
multiselect
options={hospitalOptions}
selectedValues={hospitals}
onSelectedValuesChange={setHospitals}
searchable
showSelectAll
showClear
appendTo="body"
/>

// Custom dropdown content
<Filter text={periodLabel} selected={!!period} showClear onClear={() => setPeriod('')}>
<ChoiceGroup id="period" label="Period" inputType="radio" items={periodItems}
value={period} onChange={setPeriod} />
</Filter>
```

Group filters with `FilterGroup` to coordinate selection — radio-like by default, checkbox-like
when `multiselect`:

```tsx
<FilterGroup label="Status" value={status} onValueChange={setStatus}>
<Filter text="All" value="all" />
<Filter text="Active" value="active" />
<Filter text="Done" value="done" />
</FilterGroup>

<FilterGroup label="Tags" multiselect values={tags} onValuesChange={setTags}>
<Filter text="Urgent" value="urgent" />
<Filter text="Review" value="review" />
</FilterGroup>
```

A `FilterGroup` without any of `value` / `defaultValue` / `values` / `defaultValues` /
`onValueChange` / `onValuesChange` / `label` / `multiselect` is **unmanaged** — children
behave as standalone toggles and the wrapper exists only for visual grouping.

Variants and customisation:
- `variant?: 'primary' | 'secondary'`, `size?: 'default' | 'large'`
- `prepend` / `append` for icons or badges. `hidePrependWhenSelected` (default `true`) swaps
the prepend slot for the check icon when the filter becomes selected.
- `appendTo: 'body' | HTMLElement` portals the dropdown out of the trigger's stacking context.
- Estonian copy by default: `selectAllLabel='Vali kõik'`, `clearLabel='Tühjenda valik'`.

## Checkbox & Radio

```tsx
Expand Down
12 changes: 12 additions & 0 deletions src/tedi/components/content/calendar/calendar.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
border: var(--tedi-borders-01) solid var(--card-border-primary);
border-radius: var(--card-radius-rounded);

&--borderless {
background: none;
border: none;
border-radius: 0;
}

table {
width: 100%;
border-spacing: 0;
Expand Down Expand Up @@ -198,6 +204,12 @@
max-width: 20rem;
border: var(--tedi-borders-01) solid var(--card-border-primary);
border-radius: var(--card-radius-rounded);

&.tedi-calendar--borderless {
background: none;
border: none;
border-radius: 0;
}
}

.tedi-calendar__picker-grid-header {
Expand Down
17 changes: 14 additions & 3 deletions src/tedi/components/content/calendar/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ export interface CalendarProps extends Omit<DayPickerProps, 'mode' | 'selected'
* Optional additional CSS class for the calendar container.
*/
className?: string;
/**
* Whether to render the surrounding card (border, background, radius).
* Set to `false` when embedding inside a parent that already provides
* its own surface — e.g. alongside a calendar inside `DateTimeField`.
* The inner gradient masks and column separators are preserved either way.
* @default true
*/
bordered?: boolean;
}

export const Calendar = ({
Expand All @@ -138,8 +146,11 @@ export const Calendar = ({
applyValue,
showNavigation = true,
className,
bordered = true,
...dayPickerProps
}: CalendarProps) => {
const borderlessClass = !bordered ? styles['tedi-calendar--borderless'] : undefined;
const containerClassName = classNames(borderlessClass, className);
const isAvailable = (date: Date) => {
if (!availableDays) return true;

Expand Down Expand Up @@ -202,7 +213,7 @@ export const Calendar = ({
setView('months');
}
}}
className={className}
className={containerClassName}
/>
)}

Expand All @@ -221,7 +232,7 @@ export const Calendar = ({
setView('days');
}
}}
className={className}
className={containerClassName}
/>
)}

Expand Down Expand Up @@ -251,7 +262,7 @@ export const Calendar = ({
}}
footer={footer}
classNames={{
root: classNames(styles['tedi-calendar'], className),
root: classNames(styles['tedi-calendar'], borderlessClass, className),
month_caption: styles['tedi-calendar__caption'],
head: styles['tedi-calendar__head'],
row: styles['tedi-calendar__row'],
Expand Down
11 changes: 11 additions & 0 deletions src/tedi/components/filter/filter/filter-group-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createContext } from 'react';

export interface FilterGroupContextValue {
isManaged: boolean;
multiselect: boolean;
disabled: boolean;
isSelected: (value: string) => boolean;
selectFilter: (value: string) => void;
}

export const FilterGroupContext = createContext<FilterGroupContextValue | null>(null);
Loading
Loading