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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
}

&--with-week-numbers {

.tedi-calendar-day-grid__header,
.tedi-calendar-day-grid__row {
grid-template-columns: repeat(8, var(--form-calendar-date-width));
Expand Down Expand Up @@ -191,3 +190,46 @@
pointer-events: none;
}
}

.tedi-calendar--full-width .tedi-calendar-day-grid {
width: 100%;

&__row {
min-height: var(--form-calendar-date-width);
}

&__header,
&__row {
grid-template-columns: repeat(7, minmax(0, 1fr));
}

&--with-week-numbers {
.tedi-calendar-day-grid__header,
.tedi-calendar-day-grid__row {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
}

&__weekday,
&__week-number-header,
&__week-number {
width: 100%;
}

&__day {
width: min(100%, var(--form-calendar-date-width));
height: auto;
aspect-ratio: 1;

&--range-start,
&--range-end,
&--range-middle,
&--range-preview-start,
&--range-preview-end,
&--range-preview-middle {
width: 100%;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
height: var(--form-calendar-date-width);
aspect-ratio: auto;
}
}
}
18 changes: 14 additions & 4 deletions src/tedi/components/content/calendar/calendar.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
// --_tedi-calendar-month-count keeps it correct before/without the measurement
// (e.g. SSR): all months side by side.
width: calc(
var(
--_tedi-calendar-fit-columns,
var(--_tedi-calendar-month-count, 1)
) * var(--_tedi-calendar-month-width)
var(--_tedi-calendar-fit-columns, var(--_tedi-calendar-month-count, 1)) *
var(--_tedi-calendar-month-width)
);
min-width: var(--_tedi-calendar-month-width);
height: fit-content;
Expand All @@ -34,6 +32,18 @@
--_tedi-calendar-day-columns: 8;
}

&--full-width {
box-sizing: border-box;
width: 100%;
min-width: 0;

.tedi-calendar__month {
box-sizing: border-box;
flex: 1 1 var(--_tedi-calendar-month-width);
min-width: min(100%, var(--_tedi-calendar-month-width));
}
}

&__months {
display: flex;
flex-wrap: wrap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ describe("CalendarComponent", () => {
expect(grids.length).toBe(1);
});

it("only applies the full-width layout when requested", () => {
const host = fixture.nativeElement as HTMLElement;
expect(host.classList).not.toContain("tedi-calendar--full-width");
fixture.componentRef.setInput("fullWidth", true);
fixture.detectChanges();
expect(host.classList).toContain("tedi-calendar--full-width");
});

it("renders the header", () => {
const header = fixture.debugElement.query(
By.css(".tedi-calendar-header"),
Expand Down
3 changes: 3 additions & 0 deletions src/tedi/components/content/calendar/calendar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const YEAR_PAGE_SIZE = 12;
"[class.tedi-calendar--multi-month]": "numberOfMonths() > 1",
"[class.tedi-calendar--with-week-numbers]": "showWeekNumbers()",
"[class.tedi-calendar--bordered]": "bordered()",
"[class.tedi-calendar--full-width]": "fullWidth()",
"[style.--_tedi-calendar-month-count]": "numberOfMonths()",
},
})
Expand Down Expand Up @@ -125,6 +126,8 @@ export class CalendarComponent implements ControlValueAccessor {
* DateField overlay).
*/
readonly bordered = input<boolean>(true);
/** Fill the available container width, distributing day columns evenly. */
readonly fullWidth = input<boolean>(false);
/**
* Matchers that mark dates as disabled. Each matcher can be a `Date`, `Date[]`,
* `{ before }`, `{ after }`, `{ before, after }`, `{ from, to? }`,
Expand Down
11 changes: 11 additions & 0 deletions src/tedi/components/content/calendar/calendar.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ export default {
defaultValue: { summary: "true" },
},
},
fullWidth: {
description:
"Fill the parent width and distribute weekday/date columns evenly. Ordinary day buttons retain their usual size; range backgrounds span their columns.",
control: { type: "boolean" },
table: {
category: "inputs",
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
numberOfMonths: {
description:
"How many consecutive months to render side by side. Useful for date-range selection.",
Expand Down Expand Up @@ -365,6 +375,7 @@ export const Default: Story = {
showWeekNumbers: false,
showNavigation: true,
numberOfMonths: 1,
fullWidth: false,
required: false,
inputDisabled: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function makeData(
showOutsideDays: true,
showWeekNumbers: false,
numberOfMonths: 1,
calendarFullWidth: false,
monthYearSelectType: "dropdown",
required: false,
disabledMatchers: [],
Expand Down Expand Up @@ -70,6 +71,14 @@ describe("DateFieldModalComponent", () => {
expect(component).toBeTruthy();
});

it("enables the full-width layout on the modal calendar", () => {
setup(makeData({ calendarFullWidth: true }));
const modal = fixture.nativeElement.querySelector(".tedi-date-field-modal");
const calendar = fixture.nativeElement.querySelector(".tedi-calendar");
expect(modal.classList).toContain("tedi-date-field-modal--full-width");
expect(calendar.classList).toContain("tedi-calendar--full-width");
});

it("seeds draft from data.value", () => {
const value = new Date(2026, 4, 14);
setup(makeData({ value }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface DateFieldModalData {
showOutsideDays: boolean;
showWeekNumbers: boolean;
numberOfMonths: number;
calendarFullWidth: boolean;
monthYearSelectType: "dropdown" | "grid";
required: boolean;
disabledMatchers: Matcher[];
Expand Down Expand Up @@ -62,7 +63,10 @@ export interface DateFieldModalData {
TediTranslationPipe,
],
template: `
<tedi-modal class="tedi-date-field-modal">
<tedi-modal
class="tedi-date-field-modal"
[class.tedi-date-field-modal--full-width]="data.calendarFullWidth"
>
<tedi-modal-header>
<h2>{{ "date-field.modal-title" | tediTranslate }}</h2>
</tedi-modal-header>
Expand All @@ -77,6 +81,7 @@ export interface DateFieldModalData {
[showOutsideDays]="data.showOutsideDays"
[showWeekNumbers]="data.showWeekNumbers"
[numberOfMonths]="data.numberOfMonths"
[fullWidth]="data.calendarFullWidth"
[monthYearSelectType]="data.monthYearSelectType"
[required]="data.required"
[disabledMatchers]="data.disabledMatchers"
Expand Down Expand Up @@ -116,6 +121,10 @@ export interface DateFieldModalData {
align-items: center;
justify-content: center;
}
.tedi-date-field-modal--full-width .tedi-date-field-modal__content {
align-items: stretch;
justify-content: flex-start;
}
`,
],
})
Expand Down
15 changes: 15 additions & 0 deletions src/tedi/components/form/date-field/date-field.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,21 @@ describe("DateFieldComponent", () => {
});

describe("modal-below-breakpoint", () => {
it("passes the opt-in full-width layout to the modal and removes the compact cap", () => {
const { component, modalService } = createField({
modal: true,
calendarFullWidth: true,
});
modalService.open.mockReturnValue({
closed: { subscribe: jest.fn() },
});

component.handleIconClick();
const config = modalService.open.mock.calls[0][1];
expect(config.data.calendarFullWidth).toBe(true);
expect(config.maxWidth).toBeUndefined();
});

it("opens a modal when below the configured breakpoint", () => {
const { component, modalService } = createField({ modal: "md" });
const bp = TestBed.inject(
Expand Down
7 changes: 6 additions & 1 deletion src/tedi/components/form/date-field/date-field.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ export class DateFieldComponent
readonly modal = input<DateFieldModalInput>(false);
/** Make the modal fullscreen: `true` always, `false` never, breakpoint name → fullscreen below that breakpoint. Only applies when the calendar actually opens as a modal. */
readonly fullscreen = input<ModalFullscreen>(false);
/** Stretch the calendar across the modal body. Only applies in modal mode. */
readonly calendarFullWidth = input(false);
/**
* Custom formatter for the input's display string. Overrides the locale-aware
* default. Receives the `DateFieldValue` and returns a string.
Expand Down Expand Up @@ -849,6 +851,7 @@ export class DateFieldComponent
showOutsideDays: this.showOutsideDays(),
showWeekNumbers: this.showWeekNumbers(),
numberOfMonths: this.numberOfMonthsResolved(),
calendarFullWidth: this.calendarFullWidth(),
monthYearSelectType: this.monthYearSelectType(),
required: this.required(),
disabledMatchers: this.resolvedDisabledMatchers(),
Expand All @@ -873,7 +876,9 @@ export class DateFieldComponent
// month count so a multi-month calendar fits side by side, and add the
// modal's two outer borders so the border-box content leaves room for
// it without a 2px horizontal scroll.
maxWidth: `calc(${this.numberOfMonthsResolved()} * var(--tedi-containers-03) + 2 * var(--tedi-borders-01))`,
maxWidth: this.calendarFullWidth()
? undefined
: `calc(${this.numberOfMonthsResolved()} * var(--tedi-containers-03) + 2 * var(--tedi-borders-01))`,
},
);
this.modalRef = ref;
Expand Down
18 changes: 16 additions & 2 deletions src/tedi/components/form/date-field/date-field.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const COMMON_INPUTS = [
"hideOnScroll",
"modal",
"fullscreen",
"calendarFullWidth",
"calendarTrigger",
"numberOfMonths",
];
Expand Down Expand Up @@ -160,6 +161,7 @@ const renderSingle: NonNullable<StoryObj<DateFieldStoryArgs>["render"]> = (
[hideOnScroll]="hideOnScroll"
[modal]="modal"
[fullscreen]="fullscreen"
[calendarFullWidth]="calendarFullWidth"
[calendarTrigger]="calendarTrigger"
[numberOfMonths]="numberOfMonths"
[minDate]="minDate"
Expand Down Expand Up @@ -244,6 +246,7 @@ export default {
hideOnScroll: false,
modal: false,
fullscreen: false,
calendarFullWidth: false,
calendarTrigger: "button",
numberOfMonths: 1,
minYear: null,
Expand Down Expand Up @@ -616,6 +619,16 @@ export default {
defaultValue: { summary: "false" },
},
},
calendarFullWidth: {
description:
"Stretch the calendar across the modal body while keeping ordinary date buttons at their usual size. Only applies in modal mode.",
control: { type: "boolean" },
table: {
category: "inputs",
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
},
calendarTrigger: {
description:
"What opens the calendar. `button` opens it from the icon button; `input` also opens it when the text input is focused. Accepts a `BreakpointInput` for per-breakpoint behaviour.",
Expand Down Expand Up @@ -1193,14 +1206,15 @@ export const MobileModal: Story = {
</tedi-form-field>
</tedi-col>
<tedi-col>
<p tedi-text modifiers="small bold">Fullscreen modal (modal=true, fullscreen=true)</p>
<p tedi-text modifiers="small bold">Fullscreen modal with full-width calendar</p>
<tedi-form-field>
<label tedi-label for="date-modal-fullscreen">Kuupäev</label>
<tedi-date-field
inputId="date-modal-fullscreen"
[formControl]="fullscreenControl"
[modal]="true"
[fullscreen]="true"
[calendarFullWidth]="true"
[useNativePicker]="false"
/>
</tedi-form-field>
Expand All @@ -1214,7 +1228,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 Cancel/Confirm. The first field uses the compact centered layout; the second combines `[fullscreen]="true"` with `[calendarFullWidth]="true"` to spread the calendar across the fullscreen modal. Both `modal` and `fullscreen` accept `true | false | sm | md | lg | xl`.',
},
},
},
Expand Down
Loading