diff --git a/app/lib.ts b/app/lib.ts index 25ee8c03..ddb7ed56 100644 --- a/app/lib.ts +++ b/app/lib.ts @@ -30,6 +30,7 @@ export { SubmitButton } from '../src/components/forms/context/SubmitButton/Submi // Forms > Controls export { Checkbox } from '../src/components/forms/controls/Checkbox/Checkbox.tsx'; +export { DatePicker } from '../src/components/forms/controls/DatePicker/DatePicker.tsx'; export { Input } from '../src/components/forms/controls/Input/Input.tsx'; export { Radio } from '../src/components/forms/controls/Radio/Radio.tsx'; export { SegmentedControl } from '../src/components/forms/controls/SegmentedControl/SegmentedControl.tsx'; @@ -40,6 +41,7 @@ export { Switch } from '../src/components/forms/controls/Switch/Switch.tsx'; export { CheckboxField } from '../src/components/forms/fields/CheckboxField/CheckboxField.tsx'; export { CheckboxGroup } from '../src/components/forms/fields/CheckboxGroup/CheckboxGroup.tsx'; export { InputField } from '../src/components/forms/fields/InputField/InputField.tsx'; +export { InputFieldWithTags } from '../src/components/forms/fields/InputFieldWithTags/InputFieldWithTags.tsx'; export { RadioField } from '../src/components/forms/fields/RadioField/RadioField.tsx'; export { RadioGroup } from '../src/components/forms/fields/RadioGroup/RadioGroup.tsx'; diff --git a/package-lock.json b/package-lock.json index 36875ba5..4735a194 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "message-tag": "^0.10.0", "optics-ts": "^2.4.1", "react": "^19.0.0-rc.1", + "react-datepicker": "^7.5.0", "react-dom": "^19.0.0-rc.1", "react-error-boundary": "^4.1.2", "react-hook-form": "^7.53.2", @@ -8224,7 +8225,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -8839,7 +8839,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -9496,7 +9495,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10582,6 +10580,23 @@ "node": ">= 0.6.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -10713,6 +10728,32 @@ "react": "^16.3.0 || ^17.0.1 || ^18.0.0" } }, + "node_modules/react-datepicker": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.5.0.tgz", + "integrity": "sha512-6MzeamV8cWSOcduwePHfGqY40acuGlS1cG//ePHT6bVbLxWyqngaStenfH03n1wbzOibFggF66kWaBTb1SbTtQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.23", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-datepicker/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/react-docgen": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.0.3.tgz", diff --git a/package.json b/package.json index f252c9bb..df32b14b 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ "react-toastify": "^10.0.6", "effect": "^3.10.15", "react-hook-form": "^7.53.2", - "optics-ts": "^2.4.1" + "optics-ts": "^2.4.1", + "react-datepicker": "^7.5.0" }, "overrides": { "@types/react": "npm:types-react@rc", diff --git a/package.json.js b/package.json.js index 6b5a9781..b1d64a26 100644 --- a/package.json.js +++ b/package.json.js @@ -13,7 +13,7 @@ const packageConfig = { author: 'Fortanix', description: 'Fortanix Baklava design system', repository: { type: 'git', url: 'git+https://github.com/fortanix/baklava.git' }, - + files: [ 'src', 'app', @@ -34,77 +34,77 @@ const packageConfig = { 'default': './src/styling/variables.scss', }, }, - + scripts: { // Utilities 'gen-package': 'node package.json.js', // Update `package.json` // Use --force currently since we're using React v19 rc. Once peer deps are updated remove this. 'install-project': 'npm run gen-package && npm install --force', 'ci-project': 'npm ci --force', - + // CLI 'node': 'node --import=tsx', 'repl': 'tsx', 'plop': 'NODE_OPTIONS="--import tsx" plop', 'import': 'tsx scripts/import.ts', - + // Library //'lib:build': '', - + // App 'serve:dev': 'vite --config=./vite.config.ts serve', //'build': 'vite --config=./vite.config.ts --emptyOutDir build && cp src/types/vite-env.d.ts dist && echo \'{"name": "@fortanix/baklava","main": "./baklava.js"}\' > dist/package.json', 'build': 'vite --config=./vite.config.ts --emptyOutDir build', - + // Storybook 'storybook:serve': 'storybook dev -p 6006', 'storybook:build': 'storybook build --docs', - + // Static analysis 'check:types': 'tsc --noEmit', 'lint:style': `stylelint 'src/**/*.scss'`, 'lint:script': 'biome lint', 'lint': 'npm run lint:style && npm run lint:script', - + // Test // Note: use `vitest run --root=. src/...` to run a single test file //'test': 'vitest run --root=.', // Need to specify `--root=.` since the vite root is set to `./app` 'test': 'npm run check:types && npm run lint:style', 'test-ui': 'vitest --ui', 'coverage': 'vitest run --coverage', - + // Shorthands 'start': 'npm run storybook:serve', - + // Hooks 'prepare': 'npm run build', }, - + // Dev dependencies (only needed when building, or making changes to the code) devDependencies: { // CLI 'plop': '^4.0.1', 'tsx': '^4.19.2', 'glob': '^11.0.0', - + // Build 'vite': '^5.4.11', '@vitejs/plugin-react': '^4.3.3', 'vite-plugin-dts': '^4.3.0', 'vite-plugin-lib-inject-css': '^2.1.1', 'vite-plugin-svg-icons': '^2.0.1', - + // Static analysis 'typescript': '^5.6.3', '@types/node': '^22.9.0', 'stylelint': '^16.10.0', 'stylelint-config-standard-scss': '^13.1.0', '@biomejs/biome': '^1.9.4', - + // Testing 'vitest': '^2.1.5', '@vitest/ui': '^2.1.5', - + // Storybook 'storybook': '^8.4.4', '@storybook/react': '^8.4.4', @@ -122,7 +122,7 @@ const packageConfig = { 'storybook-dark-mode': '^4.0.2', '@percy/cli': '^1.30.2', '@percy/storybook': '^6.0.2', - + // Styling 'vite-css-modules': '^1.6.0', 'typescript-plugin-css-modules': '^5.0.1', @@ -134,33 +134,35 @@ const packageConfig = { //'@csstools/postcss-light-dark-function': '^1.0.5', 'postcss-color-contrast': '^1.1.0', 'lightningcss': '^1.28.1', - + // React //'@types/react': '^18.3.7', //'@types/react-dom': '^18.2.22', '@types/react': 'npm:types-react@rc', '@types/react-dom': 'npm:types-react-dom@rc', }, - + // Dependencies needed when running the generated build dependencies: { // Utilities 'date-fns': '^4.1.0', 'message-tag': '^0.10.0', - + // React 'classnames': '^2.5.1', 'react': '^19.0.0-rc.1', 'react-dom': '^19.0.0-rc.1', 'react-error-boundary': '^4.1.2', - + '@floating-ui/react': '^0.26.28', 'react-toastify': '^10.0.6', - + 'effect': '^3.10.15', 'react-hook-form': '^7.53.2', - + 'optics-ts': '^2.4.1', + + 'react-datepicker': '^7.5.0', }, overrides: { // See https://react.dev/blog/2024/04/25/react-19-upgrade-guide#installing @@ -176,7 +178,7 @@ const packageConfig = { const packageConfigWithComment = { // http://stackoverflow.com/questions/14221579/how-do-i-add-comments-to-package-json '//': 'NOTE: This is a generated file. Do not edit this file directly, use package.json.js instead.', - + ...packageConfig, }; diff --git a/src/components/forms/controls/DatePicker/DatePicker.module.scss b/src/components/forms/controls/DatePicker/DatePicker.module.scss new file mode 100644 index 00000000..79bd6b0c --- /dev/null +++ b/src/components/forms/controls/DatePicker/DatePicker.module.scss @@ -0,0 +1,142 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@use '../../../../styling/defs.scss' as bk; + +// TODO: copied manually from old Baklava, where should we put this? +$z-index--date-picker: 100; + +@layer baklava.components { + .bk-datepicker { + @include bk.component-base(bk-datepicker); + + width: 12rem; + display: flex; + flex-direction: row; + align-items: center; + + .bk-date-picker__date { + text-align: right; + } + } +} + +// DATEPICKER override react-datepicker styles + +.react-datepicker-popper { + z-index: $z-index--date-picker; +} +.react-datepicker { + color: red !important; // DEBUG + width: 30rem; + text-align: center; + margin-top: 0.5rem; + padding: 2.5rem; + //background-color: bkl.$light-color-3; // TODO + border-radius: 0.4rem !important; + border-width: 0 !important; + font-family: bk.$font-family-body; + font-size: bk.$font-size-m; + font-weight: bk.$font-weight-regular; + &__triangle, + &__triangle::before { + border: none !important; + } + &__header { + background: transparent; + border: 0; + //color: bkl.$brand-color-dark-2; // TODO color + font-family: bk.$font-family-display; + font-weight: bk.$font-weight-regular; + } + &__month-container { + display: inline-block; + width: 100%; + float: none; + } + &__current-month { + //color: bkl.$brand-color-dark-2; // TODO color + font-size: 2rem; // TODO font-size + margin-bottom: 1rem; + font-family: bk.$font-family-display; + font-weight: bk.$font-weight-regular; + } + &__navigation { + position: absolute; + top: 3.3rem; + width: 3rem; + border: none; + text-indent: -999em; + &-icon { + &:before, &:after { + display: flex; + } + } + &--previous { + background: url('../../../../assets/icons_old/arrow-left.svg') no-repeat center center; // TODO maybe new icon? + background-size: 1.8rem; + left: 2rem; + &:hover { + opacity: 0.7; + } + } + &--next { + background: url('../../../../assets/icons_old/arrow-right.svg') no-repeat center center; // TODO maybe new icon? + background-size: 1.8rem; + right: 2rem; + &:hover { + opacity: 0.7; + } + } + } + &__day-names { + border-bottom: 0.1rem solid #d0d0d0; // TODO hardcoded color + } + &__day-name, + &__day { + display: inline-block; + width: 3.5rem; + line-height: 3.5rem; + margin: 0; + color: #454545; // TODO hardcoded color + &--outside-month { + opacity: .7; + } + &--disabled { + opacity: .35; + } + } + &__day { + &--selected, + &--keyboard-selected { + // background: bkl.$accent-color; // TODO color + border-radius: 50%; + } + &:not(&--selected):hover { + // background: rgba(bkl.$accent-color, 0.5); // TODO color + border-radius: 50%; + } + &--in-range, + &--in-selecting-range { + background: transparent; + background-color: transparent; + border-radius: 0; + } + } +} +.react-datepicker-popper[data-placement^="bottom"] { + margin-top: 0; +} + +.react-datepicker-popper[data-placement^="top"] { + margin-bottom: 0; +} + +.react-datepicker__month--selecting-range .react-datepicker__day--in-range:not(.react-datepicker__day--in-selecting-range), +.react-datepicker__month--selecting-range .react-datepicker__day--in-selecting-range:not(.react-datepicker__day--in-range) { + background: transparent; + background-color: transparent; + border-radius: 0; + color: #454545; // TODO hardcoded color +} diff --git a/src/components/forms/controls/DatePicker/DatePicker.stories.tsx b/src/components/forms/controls/DatePicker/DatePicker.stories.tsx new file mode 100644 index 00000000..7a35fdbe --- /dev/null +++ b/src/components/forms/controls/DatePicker/DatePicker.stories.tsx @@ -0,0 +1,40 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import type { Meta, StoryObj } from '@storybook/react'; + +import * as React from 'react'; + +import { DatePicker } from './DatePicker.tsx'; + + +type DatePickerArgs = React.ComponentProps; +type Story = StoryObj; + +export default { + component: DatePicker, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + }, + args: {}, + decorators: [ + Story =>
{ event.preventDefault(); }}>, + ], +} satisfies Meta; + +export const DatePickerStory: Story = { + name: 'Date Picker', + render: (args) => { + const [startDate, setStartDate] = React.useState(new Date()); + + return ( +
+ setStartDate(date)} /> +
+ ); + } +}; diff --git a/src/components/forms/controls/DatePicker/DatePicker.tsx b/src/components/forms/controls/DatePicker/DatePicker.tsx new file mode 100644 index 00000000..64290733 --- /dev/null +++ b/src/components/forms/controls/DatePicker/DatePicker.tsx @@ -0,0 +1,59 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; +import ReactDatePicker from 'react-datepicker'; + +import { classNames as cx, type ComponentPropsWithoutRef, type ClassNameArgument } from '../../../../util/componentUtil.ts'; + +import 'react-datepicker/dist/react-datepicker.css'; +import cl from './DatePicker.module.scss'; + + +export type DatePickerProps = Omit, 'onChange'> & { + /** Whether this component should be unstyled. */ + unstyled?: undefined | boolean, + + className?: ClassNameArgument, + + date: Date, + maxDate?: Date, + minDate?: Date, + onChange: (date: Date) => void, +}; + +/** A wrapper for ReactDatePicker */ +export const DatePicker = (props: DatePickerProps) => { + const { + unstyled = false, + className, + date, + maxDate, + minDate, + onChange, + ...propsRest + } = props; + + return ( +
+ +
+ ); +}; diff --git a/src/util/componentUtil.ts b/src/util/componentUtil.ts index 333d2694..fdf75057 100644 --- a/src/util/componentUtil.ts +++ b/src/util/componentUtil.ts @@ -17,3 +17,8 @@ export type ComponentProps = Omit, 'className'> & { className?: undefined | ClassNameArgument, }; + +export type ComponentPropsWithoutRef = + Omit, 'className'> & { + className?: undefined | ClassNameArgument, + };