Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
9 changes: 6 additions & 3 deletions libs/form-component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,27 @@
"scripts": {
"test": "vitest",
"test:ci": "vitest run",
"lint": "eslint \"src/**/*.ts*\"",
"typecheck": "tsc --noEmit -p ./",
"storybook": "storybook dev -p 6008",
"build-storybook": "storybook build"
},
"dependencies": {
"classnames": "2.5.1"
"classnames": "2.5.1",
"date-fns": "^4.1.0",
"react-day-picker": "^9.14.0",
"react-number-format": "^5.4.5"
},
"peerDependencies": {
"@digdir/designsystemet-react": "^1.11.1",
"classnames": "2.5.1",
"@navikt/aksel-icons": "^8.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@chromatic-com/storybook": "^5.0.1",
"@digdir/designsystemet-css": "1.11.1",
"@digdir/designsystemet-react": "1.11.1",
"@navikt/aksel-icons": "8.9.0",
"@storybook/addon-docs": "10.2.16",
"@storybook/addon-links": "10.2.16",
"@storybook/builder-vite": "10.2.16",
Expand Down
3 changes: 1 addition & 2 deletions libs/form-component/src/app-components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { PropsWithChildren } from 'react';

import { Button as DesignSystemButton } from '@digdir/designsystemet-react';
import type { ButtonProps as DesignSystemButtonProps } from '@digdir/designsystemet-react';

import { Spinner } from '../Spinner';
import { Spinner } from '@app/form-component';

export type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | undefined;
export type ButtonColor = 'first' | 'second' | 'success' | 'danger' | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import { DayPicker } from 'react-day-picker';
import type { Matcher, MonthCaption } from 'react-day-picker';

import styles from 'src/app-components/Datepicker/Calendar.module.css';
import { getLocale } from 'src/app-components/Datepicker/utils/dateHelpers';
import styles from './Calendar.module.css';
import { getLocale } from './utils/dateHelpers';

export interface CalendarDialogProps {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import type { Ref } from 'react';
import { Textfield } from '@digdir/designsystemet-react';
import { format, isValid } from 'date-fns';

import styles from 'src/app-components/Datepicker/Calendar.module.css';
import styles from './Calendar.module.css';
import {
dateFormatCanBeNumericInReactPatternFormat,
getFormatAsPatternFormat,
getSaveFormattedDateString,
strictParseFormat,
strictParseISO,
} from 'src/app-components/Datepicker/utils/dateHelpers';
} from './utils/dateHelpers';

export interface DatePickerInputProps {
id: string;
Expand All @@ -25,7 +25,15 @@ export interface DatePickerInputProps {
}

function DatePickerInputRef(
{ id, value, datepickerFormat, timeStamp, onValueChange, readOnly, autoComplete }: DatePickerInputProps,
{
id,
value,
datepickerFormat,
timeStamp,
onValueChange,
readOnly,
autoComplete,
}: DatePickerInputProps,
ref: Ref<HTMLInputElement>,
) {
const dateValue = strictParseISO(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState } from 'react';
import type { MonthCaptionProps } from 'react-day-picker';

import type { Meta, StoryObj } from '@storybook/react-vite';

import 'react-day-picker/style.css';

import { DatePickerControl } from './Datepicker';

const NoopDropdownCaption = ({ calendarMonth }: MonthCaptionProps) => (
<div style={{ padding: 8, fontWeight: 600 }}>
{calendarMonth.date.toLocaleString('en', { month: 'long', year: 'numeric' })}
</div>
);

const meta = {
title: 'AppComponents/DatePickerControl',
component: DatePickerControl,
} satisfies Meta<typeof DatePickerControl>;
Comment thread
adamhaeger marked this conversation as resolved.

export default meta;

type Story = StoryObj<typeof meta>;

const Wrapper = (args: React.ComponentProps<typeof DatePickerControl>) => {
const [value, setValue] = useState(args.value);
return <DatePickerControl {...args} value={value} onValueChange={setValue} />;
};

export const Preview: Story = {
args: {
id: 'datepicker-preview',
value: '2025-03-15',
dateFormat: 'dd.MM.yyyy',
locale: 'nb',
buttonAriaLabel: 'Open date picker',
calendarIconTitle: 'Calendar',
DropdownCaption: NoopDropdownCaption,
},
render: (args) => <Wrapper {...args} />,
};

export const ReadOnly: Story = {
args: {
...Preview.args,
readOnly: true,
},
render: (args) => <Wrapper {...args} />,
};

export const WithMinMax: Story = {
args: {
...Preview.args,
minDate: new Date('2025-01-01'),
maxDate: new Date('2025-12-31'),
},
render: (args) => <Wrapper {...args} />,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { MonthCaption } from 'react-day-picker';
import { CalendarIcon } from '@navikt/aksel-icons';
import { isValid as isValidDate } from 'date-fns';

import styles from 'src/app-components/Datepicker/Calendar.module.css';
import { DatePickerCalendar } from 'src/app-components/Datepicker/DatePickerCalendar';
import { DatePickerDialog } from 'src/app-components/Datepicker/DatepickerDialog';
import { DatePickerInput } from 'src/app-components/Datepicker/DatePickerInput';
import { getSaveFormattedDateString } from 'src/app-components/Datepicker/utils/dateHelpers';
import { Flex } from 'src/app-components/Flex/Flex';
import { Flex } from '@app/form-component';
import styles from './Calendar.module.css';
import { DatePickerCalendar } from './DatePickerCalendar';
import { DatePickerDialog } from './DatepickerDialog';
import { DatePickerInput } from './DatePickerInput';
import { getSaveFormattedDateString } from './utils/dateHelpers';

export type DatePickerControlProps = {
id: string;
Expand Down Expand Up @@ -60,11 +60,7 @@ export const DatePickerControl: React.FC<DatePickerControlProps> = ({
};

return (
<Flex
container
item
size={{ xs: 12 }}
>
<Flex container item size={{ xs: 12 }}>
<div className={styles.calendarInputWrapper}>
<DatePickerInput
ref={inputRef}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { createContext, useContext, useEffect, useRef } from 'react';
import { createContext, useContext, useEffect, useRef } from 'react';
import type { PropsWithChildren, ReactNode } from 'react';

import { Dialog, Popover } from '@digdir/designsystemet-react';

import styles from 'src/app-components/Datepicker/Calendar.module.css';
import { useIsMobile } from 'src/app-components/hooks/useDeviceWidths';
import styles from './Calendar.module.css';
import { useIsMobile } from '@app/form-component';

const DatePickerCloseContext = createContext<(() => void) | null>(null);

Expand Down
28 changes: 28 additions & 0 deletions libs/form-component/src/app-components/Datepicker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export { DatePickerControl } from './Datepicker';
export type { DatePickerControlProps } from './Datepicker';
export { DatePickerCalendar } from './DatePickerCalendar';
export type { CalendarDialogProps } from './DatePickerCalendar';
export { DatePickerInput } from './DatePickerInput';
export type { DatePickerInputProps } from './DatePickerInput';
export { DatePickerDialog, useDatePickerClose } from './DatepickerDialog';
export {
DateFlags,
DatepickerMinDateDefault,
DatepickerMaxDateDefault,
DatepickerFormatDefault,
PrettyDateAndTime,
getDateFormat,
getSaveFormattedDateString,
getDateConstraint,
formatISOString,
isDate,
getLocale,
getDateLib,
strictParseISO,
strictParseFormat,
dateFormatCanBeNumericInReactPatternFormat,
getFormatAsPatternFormat,
getMonths,
getYears,
} from './utils/dateHelpers';
export type { Token } from './utils/dateHelpers';
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { jest } from '@jest/globals';
import { formatISO, isValid, parseISO } from 'date-fns';

import {
Expand All @@ -10,18 +9,21 @@ import {
getDateFormat,
getSaveFormattedDateString,
strictParseISO,
} from 'src/app-components/Datepicker/utils/dateHelpers';
} from './dateHelpers';

describe('dateHelpers', () => {
beforeAll(() => {
/**
* Mock todays date to be 2023-07-07T12:54:25.000Z
*/
jest.useFakeTimers({ now: 1688734465000 });
vi.useFakeTimers({ now: 1688734465000 });
});

describe('getDateFormat', () => {
const tests: { props: Parameters<typeof getDateFormat>; expected: ReturnType<typeof getDateFormat> }[] = [
const tests: {
props: Parameters<typeof getDateFormat>;
expected: ReturnType<typeof getDateFormat>;
}[] = [
{ props: ['YYYY-MM-DD'], expected: 'yyyy-MM-dd' },
{ props: ['DD/MM/YYYY'], expected: 'dd/MM/yyyy' },
{ props: ['DD.MM.YYYY'], expected: 'dd.MM.yyyy' },
Expand Down Expand Up @@ -127,7 +129,10 @@ describe('dateHelpers', () => {
});

describe('formatISOString', () => {
const tests: { props: Parameters<typeof formatISOString>; expected: ReturnType<typeof formatISOString> }[] = [
const tests: {
props: Parameters<typeof formatISOString>;
expected: ReturnType<typeof formatISOString>;
}[] = [
{ props: [undefined, 'dd/MM/yyyy'], expected: null },
{ props: ['2023-13-01', 'dd/MM/yyyy'], expected: null },
{ props: ['2023-10-41', 'dd/MM/yyyy'], expected: null },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from 'date-fns';
import type { Locale } from 'date-fns/locale';

import { locales } from 'src/app-components/Datepicker/utils/dateLocales';
import { locales } from './dateLocales';

export enum DateFlags {
Today = 'today',
Expand Down Expand Up @@ -53,19 +53,26 @@ export function getDateFormat(format?: string, selectedLanguage = 'nb'): string
if (format) {
return convertLegacyFormat(format);
}
return getLocale(selectedLanguage).formatLong?.date({ width: 'short' }) || DatepickerFormatDefault;
return (
getLocale(selectedLanguage).formatLong?.date({ width: 'short' }) || DatepickerFormatDefault
);
}

export function getSaveFormattedDateString(date: Date | null, timestamp: boolean) {
if (date && isValid(date)) {
return (
(!timestamp ? formatISO(date, { representation: 'date' }) : formatISO(date, { representation: 'complete' })) ?? ''
(!timestamp
? formatISO(date, { representation: 'date' })
: formatISO(date, { representation: 'complete' })) ?? ''
);
}
return null;
}

export function getDateConstraint(dateOrFlag: string | DateFlags | undefined, constraint: 'min' | 'max'): Date {
export function getDateConstraint(
dateOrFlag: string | DateFlags | undefined,
Comment thread
adamhaeger marked this conversation as resolved.
constraint: 'min' | 'max',
): Date {
const shiftTime = constraint === 'min' ? startOfDay : endOfDay;

if (dateOrFlag === DateFlags.Today) {
Expand Down Expand Up @@ -103,7 +110,11 @@ export function getDateConstraint(dateOrFlag: string | DateFlags | undefined, co
}
}

export function formatISOString(isoString: string | undefined, format: string, locale?: Locale): string | null {
export function formatISOString(
isoString: string | undefined,
format: string,
locale?: Locale,
): string | null {
const date = strictParseISO(isoString);

if (date && isValid(date)) {
Expand Down Expand Up @@ -145,7 +156,10 @@ export function strictParseISO(isoString: string | undefined): Date | null {
* this function requires that the parsed date when formatted using the same format is equal to the input.
* This prevents the value in the Datepicker input from changing while typing.
*/
export function strictParseFormat(formattedDate: string | undefined, formatString: string): Date | null {
export function strictParseFormat(
formattedDate: string | undefined,
formatString: string,
): Date | null {
if (!formattedDate) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ import type { CSSProperties, ElementType, PropsWithChildren } from 'react';

import cn from 'classnames';

import classes from 'src/app-components/Flex/Flex.module.css';
import type { IGridStyling } from 'src/app-components/types';
import classes from './Flex.module.css';

type GridSize = 'auto' | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;

export interface IGridStyling {
xs?: GridSize;
sm?: GridSize;
md?: GridSize;
lg?: GridSize;
xl?: GridSize;
}

type Spacing = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;

Expand Down
2 changes: 2 additions & 0 deletions libs/form-component/src/app-components/Flex/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Flex } from './Flex';
export type { IGridStyling } from './Flex';
7 changes: 7 additions & 0 deletions libs/form-component/src/app-components/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
useIsMini,
useIsMobile,
useIsTablet,
useBrowserWidth,
breakpoints,
} from './useDeviceWidths';
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ type Condition = (width: number) => boolean;

const conditionIsMini: Condition = (width) => width <= breakpoints.mini;
const conditionIsMobile: Condition = (width) => width <= breakpoints.mobile;
const conditionIsTablet: Condition = (width) => width > breakpoints.mobile && width <= breakpoints.tablet;
const conditionIsTablet: Condition = (width) =>
width > breakpoints.mobile && width <= breakpoints.tablet;

export function useIsMini() {
return useBrowserWidth(conditionIsMini);
Expand Down
3 changes: 3 additions & 0 deletions libs/form-component/src/app-components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export * from './AccordionItem';
export * from './Button';
export * from './Card';
export * from './Datepicker';
export * from './Flex';
export * from './hooks';
export * from './DisplayDate';
export * from './Spinner';
3 changes: 2 additions & 1 deletion src/App/frontend/monorepo-changed-paths.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ src/app-components/Accordion/
src/app-components/Button/Button.tsx
src/app-components/Card/
src/app-components/Date/
src/app-components/Datepicker/DatePickerHelpers.ts
src/app-components/Datepicker/
src/app-components/Flex/
src/app-components/Text/DisplayText.tsx
src/app-components/loading/Spinner/Spinner.tsx
src/codegen/schemas/layout-sets.schema.v1.ts
Expand Down
Loading
Loading