Skip to content
Draft
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: 5 additions & 0 deletions .changeset/lazy-wolves-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@knime/kds-components": patch
---

Add KdsIntervalInput component for ISO 8601 interval input
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ export const DesignComparator: Story = buildDesignComparatorStory({
component: KdsDateTimeFormatInput,
wrapperStyle: "width: 218px",
designsToCompare: {
".DateTimeFormatInput": {
DateTimeFormatInput: {
props: {
placeholder: "{Formatted Value}",
allDefaultFormats: dateTimeFormats,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { ref, watchEffect } from "vue";
import type { Meta, StoryObj } from "@storybook/vue3-vite";
import { useArgs } from "storybook/preview-api";
import { expect, userEvent, within } from "storybook/test";
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed

import {
buildAllCombinationsStory,
buildDesignComparatorStory,
} from "../../../test-utils/storybook";

import IntervalInputPopover from "./IntervalInputPopoverContent.vue";
import { kdsIntervalInputFormats } from "./enums";
import { createDefaultPopoverModel } from "./intervalUtils";
import type {
KdsIntervalInputUsedFormat,
KdsIntervalPopoverModel,
} from "./types";

const createDatePopoverModel = (): KdsIntervalPopoverModel => {
const model = createDefaultPopoverModel();

model.periodPart.years = 1;
model.periodPart.months = 2;
model.periodPart.days = 3;

return model;
};

const createTimePopoverModel = (): KdsIntervalPopoverModel => {
const model = createDefaultPopoverModel();

model.durationPart.hours = 1;
model.durationPart.minutes = 30;
model.durationPart.seconds = 45;

return model;
};

const meta: Meta<typeof IntervalInputPopover> = {
title: "Form Fields/IntervalInput/IntervalInputPopover",
component: IntervalInputPopover,
tags: ["autodocs"],
parameters: {
docs: {
description: {
component:
"Popover content for interval input that lets users switch between date and time interval editing, choose direction, and edit interval parts directly.",
},
},
design: {
type: "figma",
url: "https://www.figma.com/design/AqT6Q5R4KyYqUb6n5uO2XE/%F0%9F%A7%A9-kds-Components?node-id=5109-26693",
},
},
argTypes: {
modelValue: {
control: "object",
description:
"Popover model that keeps date and time interval parts in parallel while editing.",
table: { category: "model" },
},
usedFormat: {
control: { type: "select" },
options: ["date", "time"],
description: "Currently active editing mode inside the popover.",
table: { category: "model" },
},
format: {
control: { type: "select" },
options: kdsIntervalInputFormats,
description:
"Available format modes. `date_or_time` enables switching inside the popover.",
table: { category: "props" },
},
allowDescending: {
control: "boolean",
description: "Shows the forward/backward direction switch when enabled.",
table: { category: "props" },
},
},
args: {
modelValue: createDatePopoverModel(),
usedFormat: "date",
format: "date_or_time",
allowDescending: true,
},
render: (args) => {
const [, updateArgs] = useArgs();

return {
components: { IntervalInputPopover },
setup() {
const modelValue = ref(args.modelValue);
const usedFormat = ref(args.usedFormat);

watchEffect(() => {
modelValue.value = args.modelValue;
});

watchEffect(() => {
usedFormat.value = args.usedFormat;
});

watchEffect(() => {
updateArgs({
modelValue: modelValue.value,
usedFormat: usedFormat.value,
});
});

return { args, modelValue, usedFormat };
},
template:
'<IntervalInputPopover :format="args.format" :allow-descending="args.allowDescending" v-model="modelValue" v-model:used-format="usedFormat" />',
};
},
};

export default meta;

type Story = StoryObj<typeof IntervalInputPopover>;

export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const user = userEvent.setup();
const [modeGroup, directionGroup] = canvas.getAllByRole("radiogroup");
const modeScope = within(modeGroup!);
const directionScope = within(directionGroup!);

const date = modeScope.getByRole("radio", { name: "Date" });
const time = modeScope.getByRole("radio", { name: "Time" });
const forward = directionScope.getByRole("radio", { name: "Forward" });
const backward = directionScope.getByRole("radio", { name: "Backward" });

await expect(date).toHaveAttribute("aria-checked", "true");
await expect(
canvas.getByRole("spinbutton", { name: "Year" }),
).toBeInTheDocument();

await user.click(time);
await expect(time).toHaveAttribute("aria-checked", "true");
await expect(
canvas.queryByRole("spinbutton", { name: "Year" }),
).not.toBeInTheDocument();
await expect(
canvas.getByRole("spinbutton", { name: "Hour" }),
).toBeInTheDocument();

await user.click(backward);
await expect(backward).toHaveAttribute("aria-checked", "true");

backward.focus();
await user.keyboard("{ArrowLeft}");
await expect(forward).toHaveAttribute("aria-checked", "true");
await expect(forward).toHaveFocus();
},
};

export const WithTimeMode: Story = {
args: {
format: "date_or_time",
usedFormat: "time",
modelValue: createTimePopoverModel(),
},
};

export const DateOnly: Story = {
args: {
format: "date",
usedFormat: "date",
allowDescending: false,
modelValue: createDatePopoverModel(),
},
};

export const TimeOnly: Story = {
args: {
format: "time",
usedFormat: "time",
allowDescending: false,
modelValue: createTimePopoverModel(),
},
};

// TextOverflow story does not apply here.

export const DesignComparator: Story = buildDesignComparatorStory({
component: IntervalInputPopover,
wrapperStyle: "width: 373px",
designsToCompare: {
IntervalInputPopover: {
props: {
modelValue: createDatePopoverModel(),
allowDescending: true,
format: "date_or_time",
usedFormat: "date",
},
variants: {
"https://www.figma.com/design/AqT6Q5R4KyYqUb6n5uO2XE/%F0%9F%A7%A9-kds-Components?node-id=5109-26693":
{
usedFormat: "date",
parameters: {
figmaOffset: {
x: -22,
y: -20,
},
},
},
"https://www.figma.com/design/AqT6Q5R4KyYqUb6n5uO2XE/%F0%9F%A7%A9-kds-Components?node-id=17594-4369":
{
usedFormat: "time",
parameters: {
figmaOffset: {
x: -22,
y: -20,
},
},
},
},
},
},
});

export const AllCombinations: Story = buildAllCombinationsStory({
component: IntervalInputPopover,
columns: 3,
combinationsProps: {
default: {
format: ["date_or_time"],
usedFormat: ["date"] as KdsIntervalInputUsedFormat[],
modelValue: [createDatePopoverModel(), createTimePopoverModel()],
allowDescending: [true],
},
combinations: [
{
format: ["date_or_time"],
usedFormat: ["date", "time"] as KdsIntervalInputUsedFormat[],
},
{
format: ["date"],
usedFormat: ["date"] as KdsIntervalInputUsedFormat[],
allowDescending: [false, true],
},
{
format: ["time"],
usedFormat: ["time"] as KdsIntervalInputUsedFormat[],
allowDescending: [false, true],
},
],
},
});
Loading
Loading