Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
color: inherit;
}

body.sb-show-main,
body.sb-show-main #storybook-root {
body.sb-show-main {
height: auto !important;
min-height: 100vh !important;
margin: 0 !important;
box-sizing: border-box !important;
}
</style>
4 changes: 4 additions & 0 deletions community/components/form/search/search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export type AutocompleteOption = {
};
export type SearchState = "valid" | "error" | "default";

/**
* @deprecated Use the TEDI-Ready `SearchComponent` from
* `@tedi-design-system/angular/tedi` instead. This component will be removed in a future version.
*/
@Component({
selector: "tedi-search",
standalone: true,
Expand Down
2 changes: 1 addition & 1 deletion community/components/form/search/search.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default {
],
parameters: {
status: {
type: ["devComponent"],
type: ["deprecated", "existsInTediReady"],
},
},
argTypes: {
Expand Down
27 changes: 26 additions & 1 deletion skills/tedi-angular/references/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,30 @@ Wrapper that joins filters into a connected button group with collapsed borders
- `required: boolean = false`
- `invalid: boolean = false`

### Search
**Selector:** `tedi-search`
**Model:** `value: string`
**Inputs:**
- `inputId: string` (required)
- `label: string`
- `placeholder: string = ""`
- `size: SearchSize = "default"` — "small" | "default" | "large"
- `clearable: boolean = true`
- `searchIcon: string | FormFieldIcon = "search"` — ignored when `button` is set
- `button: SearchButton` — when set, renders a trailing search button (hides the inline icon). `{ text?, icon?, variant?, ariaLabel? }`; omit `text` for an icon-only button
- `feedbackText: ComponentInputs<FeedbackTextComponent>` — hint / validation message
- `disabled: boolean = false`
- `ariaLabel: string` — accessible name fallback when no visible `label`

**Outputs:**
- `searchEvent: string` — emitted on Enter or search-button click
- `clear: void` — emitted when the clear button is clicked

```html
<tedi-search inputId="q" label="Otsing" [(value)]="query" (searchEvent)="onSearch($event)" />
<tedi-search inputId="q" label="Otsing" [button]="{ text: 'Otsi' }" [formControl]="queryControl" />
```

### Slider
**Selector:** `tedi-slider` | ControlValueAccessor
**Model:** `value: number`
Expand Down Expand Up @@ -2127,7 +2151,8 @@ Import from `@tedi-design-system/angular/community`. These are community-contrib
**Selector:** `tedi-select`, `tedi-multiselect` | ControlValueAccessor
- `inputId: string`, `label: string`, `clearable: boolean = true`, `state: InputState`, `size: InputSize`

### Search
### Search — **DEPRECATED** (use TEDI-Ready Search)
**⚠️ DEPRECATED** — use the TEDI-Ready `tedi-search` from `@tedi-design-system/angular/tedi`. Same selector; the TEDI-Ready version exposes a trailing `button` (`SearchButton`), a `feedbackText` input for hints/validation, and `searchEvent` / `clear` outputs.
**Selector:** `tedi-search` | ControlValueAccessor
- `inputId: string`, `autocompleteOptions: AutocompleteOption[]`, `size: SearchSize`, `withButton: boolean`

Expand Down
3 changes: 2 additions & 1 deletion tedi/components/buttons/card-button/card-button.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { VerticalSpacingDirective } from "../../../directives/vertical-spacing/v
import { ShowAtDirective } from "../../../directives/show-at/show-at.directive";

/**
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.54.76?node-id=4620-85618&m=dev" target="_blank">Figma ↗</a>
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.54.76?node-id=4620-85618&m=dev" target="_blank">Figma ↗</a><br />
* <a href="https://www.tedi.ee/1ee8444b7/p/4191c7-card-button" target="_BLANK">Zeroheight ↗</a>
*
* CardButton is an interactive wrapper around a `tedi-card`. The host anchor
* or button provides the semantics and applies hover, active, focus and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ const PSEUDO_PARAMS = {
};

/**
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.49.74?node-id=15433-138256&m=dev" target="_blank">Figma ↗</a>
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.49.74?node-id=15433-138256&m=dev" target="_blank">Figma ↗</a><br />
* <a href="https://www.tedi.ee/1ee8444b7/p/9469bf-collapse-button" target="_BLANK">Zeroheight ↗</a>
*
* <p>
* Standalone toggle button used by <code>tedi-collapse</code> and the table's
Expand Down
4 changes: 3 additions & 1 deletion tedi/components/filter/filter.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import { RadioGroupComponent } from "../form/radio-group/radio-group.component";
import { LabelComponent } from "../form/label/label.component";

/**
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.45.70?node-id=4612-83722&m=dev" target="_blank">Figma ↗</a>
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.45.70?node-id=4612-83722&m=dev" target="_blank">Figma ↗</a><br />
* <a href="https://www.tedi.ee/1ee8444b7/p/3914a2-filter" target="_BLANK">Zeroheight ↗</a>
*/

export default {
title: "TEDI-Ready/Components/Filter",
component: FilterComponent,
Expand Down
68 changes: 42 additions & 26 deletions tedi/components/form/date-field/date-field.stories.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
type Meta,
type StoryObj,
moduleMetadata,
} from "@storybook/angular";
import { type Meta, type StoryObj, moduleMetadata } from "@storybook/angular";
import {
FormControl,
FormGroup,
Expand All @@ -23,9 +19,21 @@ import type { DateRange } from "../../content/calendar/types";
import type { Matcher } from "../../../utils/matchers.util";

const today = new Date();
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
const inThreeDays = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3);
const inTenDays = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 10);
const tomorrow = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 1,
);
const inThreeDays = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 3,
);
const inTenDays = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 10,
);

const pad = (n: number): string => n.toString().padStart(2, "0");

Expand All @@ -36,7 +44,10 @@ const formatUS = (value: Date | Date[] | DateRange | null): string => {
}
if (Array.isArray(value)) {
return value
.map((d) => `${pad(d.getMonth() + 1)}/${pad(d.getDate())}/${d.getFullYear()}`)
.map(
(d) =>
`${pad(d.getMonth() + 1)}/${pad(d.getDate())}/${d.getFullYear()}`,
)
.join(", ");
}
const from = `${pad(value.from.getMonth() + 1)}/${pad(value.from.getDate())}/${value.from.getFullYear()}`;
Expand Down Expand Up @@ -170,7 +181,7 @@ const renderSingle: NonNullable<StoryObj<DateFieldStoryArgs>["render"]> = (
};

/**
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.37.57?node-id=4620-82915&m=dev" target="_blank">Figma ↗</a><br/>
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.65.83?node-id=4620-82915&m=dev" target="_blank">Figma ↗</a><br />
* <a href="https://www.tedi.ee/1ee8444b7/p/15bd6e-date-field" target="_blank">Zeroheight ↗</a>
*
* DateField is the form-control wrapper around the Calendar. It exposes a typed text input
Expand Down Expand Up @@ -450,21 +461,22 @@ export default {
category: "inputs",
type: {
summary: "BreakpointInput<number>",
detail: "number \n{ xs: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number }",
detail:
"number \n{ xs: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number }",
},
defaultValue: { summary: "1" },
},
},
useNativePicker: {
description:
"Swaps the custom popover for the browser's native `<input type=\"date\">` UI (single mode only). `true` always uses native, `false` never; a breakpoint name (`sm | md | lg | xl`) uses native below that breakpoint and the custom popover from it upward. Defaults to `false`.",
'Swaps the custom popover for the browser\'s native `<input type="date">` UI (single mode only). `true` always uses native, `false` never; a breakpoint name (`sm | md | lg | xl`) uses native below that breakpoint and the custom popover from it upward. Defaults to `false`.',
control: { type: "select" },
options: [true, false, "sm", "md", "lg", "xl"],
table: {
category: "inputs",
type: {
summary: "DateFieldUseNativePicker",
detail: "boolean \n\"sm\" | \"md\" | \"lg\" | \"xl\"",
detail: 'boolean \n"sm" | "md" | "lg" | "xl"',
},
defaultValue: { summary: "false" },
},
Expand All @@ -478,7 +490,7 @@ export default {
category: "inputs",
type: {
summary: "DateFieldModalInput",
detail: "boolean \n\"sm\" | \"md\" | \"lg\" | \"xl\"",
detail: 'boolean \n"sm" | "md" | "lg" | "xl"',
},
defaultValue: { summary: "false" },
},
Expand All @@ -492,7 +504,7 @@ export default {
category: "inputs",
type: {
summary: "ModalFullscreen",
detail: "boolean \n\"sm\" | \"md\" | \"lg\" | \"xl\"",
detail: 'boolean \n"sm" | "md" | "lg" | "xl"',
},
defaultValue: { summary: "false" },
},
Expand Down Expand Up @@ -614,7 +626,7 @@ export const States: Story = {
docs: {
description: {
story:
"Persistent field states. Disabled is driven by the form control; the success/error states come from a `tedi-feedback-text` with `type=\"valid\"`/`\"error\"`, which the surrounding `tedi-form-field` reflects on the input border.",
'Persistent field states. Disabled is driven by the form control; the success/error states come from a `tedi-feedback-text` with `type="valid"`/`"error"`, which the surrounding `tedi-form-field` reflects on the input border.',
},
},
},
Expand Down Expand Up @@ -724,7 +736,8 @@ export const MultipleTagLayout: Story = {
render: (args) => {
const dates = Array.from(
{ length: 6 },
(_, i) => new Date(today.getFullYear(), today.getMonth(), today.getDate() + i),
(_, i) =>
new Date(today.getFullYear(), today.getMonth(), today.getDate() + i),
);
const wrapControl = new FormControl<Date[] | null>(dates);
const singleRowControl = new FormControl<Date[] | null>(dates);
Expand Down Expand Up @@ -788,7 +801,7 @@ export const OnClickType: Story = {
docs: {
description: {
story:
"`calendarTrigger` decides what opens the calendar: `\"button\"` (the icon, default) or `\"input\"` (the whole input — typing is then blocked).",
'`calendarTrigger` decides what opens the calendar: `"button"` (the icon, default) or `"input"` (the whole input — typing is then blocked).',
},
},
},
Expand Down Expand Up @@ -888,7 +901,8 @@ export const ShowWeekCount: Story = {
parameters: {
docs: {
description: {
story: "`showWeekNumbers` adds an ISO week-number column to the day grid.",
story:
"`showWeekNumbers` adds an ISO week-number column to the day grid.",
},
},
},
Expand All @@ -904,7 +918,7 @@ export const MultipleMonths: Story = {
docs: {
description: {
story:
"`numberOfMonths` shows several months side by side. A plain number is honoured at every breakpoint — `2` stays two months even on a phone or in a modal. To narrow it on small screens, pass a per-breakpoint object instead, e.g. `[numberOfMonths]=\"{ xs: 1, lg: 2 }\"`.",
'`numberOfMonths` shows several months side by side. A plain number is honoured at every breakpoint — `2` stays two months even on a phone or in a modal. To narrow it on small screens, pass a per-breakpoint object instead, e.g. `[numberOfMonths]="{ xs: 1, lg: 2 }"`.',
},
},
},
Expand All @@ -925,7 +939,7 @@ export const YearGrid: Story = {
docs: {
description: {
story:
"`monthYearSelectType=\"grid\"` replaces the header dropdowns with a clickable label that drills into a year/month grid; `selectionLevel=\"years\"` commits at year granularity. A custom `formatDate` collapses the committed `Date` (Jan 1 of the year) to just the year number.",
'`monthYearSelectType="grid"` replaces the header dropdowns with a clickable label that drills into a year/month grid; `selectionLevel="years"` commits at year granularity. A custom `formatDate` collapses the committed `Date` (Jan 1 of the year) to just the year number.',
},
},
},
Expand Down Expand Up @@ -1008,13 +1022,14 @@ export const NativePicker: Story = {
args: {
inputId: "date-native",
useNativePicker: true,
feedback: "Kasutab operatsioonisüsteemi kuupäevavalijat igal ekraanilaiusel.",
feedback:
"Kasutab operatsioonisüsteemi kuupäevavalijat igal ekraanilaiusel.",
},
parameters: {
docs: {
description: {
story:
"`[useNativePicker]=\"true\"` swaps the popover for the browser's built-in `<input type=\"date\">` UI (single mode only). The prop accepts `boolean | sm | md | lg | xl` and **defaults to `false`**. Pass a breakpoint name like `\"md\"` for native on phones and the custom popover from `md` upward.",
'`[useNativePicker]="true"` swaps the popover for the browser\'s built-in `<input type="date">` UI (single mode only). The prop accepts `boolean | sm | md | lg | xl` and **defaults to `false`**. Pass a breakpoint name like `"md"` for native on phones and the custom popover from `md` upward.',
},
},
},
Expand Down Expand Up @@ -1062,7 +1077,7 @@ export const MobileModal: Story = {
docs: {
description: {
story:
"`[modal]=\"true\"` opens the calendar in a modal (with explicit Cancel/Confirm) instead of the popover, holding the selection as a draft until Confirm. By default the modal is centered; add `[fullscreen]=\"true\"` to make it fill the screen — useful on small phones where vertical space is tight. Both `modal` and `fullscreen` accept the same union (`true | false | sm | md | lg | xl`), so e.g. `fullscreen=\"md\"` only goes fullscreen below `md`.",
'`[modal]="true"` opens the calendar in a modal (with explicit Cancel/Confirm) instead of the popover, holding the selection as a draft until Confirm. By default the modal is centered; add `[fullscreen]="true"` to make it fill the screen — useful on small phones where vertical space is tight. Both `modal` and `fullscreen` accept the same union (`true | false | sm | md | lg | xl`), so e.g. `fullscreen="md"` only goes fullscreen below `md`.',
},
},
},
Expand Down Expand Up @@ -1111,7 +1126,8 @@ export const CustomLocale: Story = {
localeCode: "en-US",
useNativePicker: false,
initialValue: inThreeDays,
feedback: "Vahetab kuude nimed, nädalapäevade nimed ja nädala esimese päeva.",
feedback:
"Vahetab kuude nimed, nädalapäevade nimed ja nädala esimese päeva.",
},
parameters: {
docs: {
Expand Down Expand Up @@ -1176,7 +1192,7 @@ export const WithReactiveForms: Story = {
docs: {
description: {
story:
"DateField implements `ControlValueAccessor`, so it slots into a `FormGroup` like any reactive control — including `mode=\"range\"`, whose value is a `{ from, to }` object. The block below the fields echoes the live `form.value`.",
'DateField implements `ControlValueAccessor`, so it slots into a `FormGroup` like any reactive control — including `mode="range"`, whose value is a `{ from, to }` object. The block below the fields echoes the live `form.value`.',
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { FeedbackTextComponent } from "./feedback-text.component";

/**
* <a href="https://www.figma.com/design/jWiRIXhHRxwVdMSimKX2FF/TEDI-READY-2.65.83?node-id=9165-62054&m=dev" target="_blank">Figma ↗</a><br />
* <a href="https://www.tedi.ee/1ee8444b7/p/67d4de-feedbacktext" target="_blank">Zeroheight ↗</a>
*/

Expand Down
4 changes: 2 additions & 2 deletions tedi/components/form/form-field/form-field.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
<div class="tedi-form-field__icon">
<tedi-icon
[name]="icon.name"
[size]="icon.size ?? (size() === 'small' ? 16 : 18)"
[color]="icon.color ?? 'secondary'"
[size]="icon.size ?? (size() === 'small' ? 16 : size() === 'large' ? 24 : 18)"
[color]="icon.color ?? 'inherit'"
[type]="icon.type ?? 'outlined'"
[variant]="icon.variant ?? 'outlined'"
[attr.aria-hidden]="true"
Expand Down
1 change: 1 addition & 0 deletions tedi/components/form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./feedback-text/feedback-text.component";
export * from "./label/label.component";
export * from "./label-row";
export * from "./number-field/number-field.component";
export * from "./search";
export * from "./select";
export * from "./slider/slider.component";
export * from "./toggle/toggle.component";
Expand Down
1 change: 1 addition & 0 deletions tedi/components/form/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./search.component";
60 changes: 60 additions & 0 deletions tedi/components/form/search/search.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<tedi-form-field
class="tedi-search__field"
[size]="size()"
[icon]="fieldIcon()"
[clearable]="clearable() && !isDisabled()"
[inputClass]="button() ? 'tedi-search__input--has-button' : null"
>
@if (label()) {
<label
tedi-label
[for]="inputId()"
[size]="size() === 'small' ? 'small' : 'default'"
>
{{ label() }}
</label>
}

<input
#searchInput
tedi-text-field
type="text"
role="searchbox"
[id]="inputId()"
[value]="value()"
[placeholder]="placeholder()"
[disabled]="isDisabled()"
[attr.aria-describedby]="feedbackId()"
(valueChange)="onInputValue($event)"
(clear)="onClear()"
(keydown.enter)="emitSearch()"
(blur)="onBlur()"
/>

@if (feedbackText(); as feedback) {
<tedi-feedback-text
[attr.id]="feedbackId()"
[text]="feedback.text"
[type]="feedback.type"
[position]="feedback.position"
/>
}
</tedi-form-field>

@if (button(); as btn) {
<button
tedi-button
type="button"
class="tedi-search__button"
[variant]="btn.variant ?? 'primary'"
[size]="buttonSize()"
[disabled]="isDisabled()"
[attr.aria-label]="buttonAriaLabel()"
(click)="emitSearch()"
>
<tedi-icon [name]="btn.icon ?? 'search'" [size]="buttonIconSize()" />
@if (btn.text) {
{{ btn.text }}
}
</button>
}
Loading
Loading