diff --git a/apps/scandic-web/components/Blocks/DynamicContent/SAS/TransferPoints/TransferPointsFormClient.tsx b/apps/scandic-web/components/Blocks/DynamicContent/SAS/TransferPoints/TransferPointsFormClient.tsx index 7f8ac4ae4..c1a23438e 100644 --- a/apps/scandic-web/components/Blocks/DynamicContent/SAS/TransferPoints/TransferPointsFormClient.tsx +++ b/apps/scandic-web/components/Blocks/DynamicContent/SAS/TransferPoints/TransferPointsFormClient.tsx @@ -16,13 +16,13 @@ import { FormProvider, useForm } from "react-hook-form" import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Input } from "@scandic-hotels/design-system/Input" import { Typography } from "@scandic-hotels/design-system/Typography" import { SAS_TRANSFER_POINT_KEY } from "@/app/[lang]/(partner)/(sas)/(protected)/sas-x-scandic/sasUtils" import Image from "@/components/Image" import Modal from "@/components/Modal" import Button from "@/components/TempDesignSystem/Button" -import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel" import styles from "./transferPoints.module.css" @@ -86,7 +86,7 @@ export function TransferPointsFormClient({
- { - label: string -} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx index a7b538631..0427f5623 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/Input/index.tsx @@ -6,8 +6,8 @@ import { Controller, useFormContext } from "react-hook-form" import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Input as InputWithLabel } from "@scandic-hotels/design-system/Input" -import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel" import Caption from "@/components/TempDesignSystem/Text/Caption" import { getErrorMessage } from "./errors" @@ -63,7 +63,7 @@ const Input = forwardRef(function Input( validationBehavior="aria" value={field.value} > - {children} -} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Label/label.module.css b/apps/scandic-web/components/TempDesignSystem/Form/Label/label.module.css deleted file mode 100644 index 0e351baf8..000000000 --- a/apps/scandic-web/components/TempDesignSystem/Form/Label/label.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.label { - color: var(--Text-Interactive-Placeholder); - font-family: "fira sans"; - font-weight: 400; - letter-spacing: 0.03px; - line-height: 120%; - text-align: left; - transition: font-size 100ms ease; - user-select: none; -} - -span.small { - display: block; - font-size: 12px; -} - -span.regular { - font-size: 16px; - order: 1; -} - -span.discreet { - color: var(--Text-Interactive-Default); - font-weight: 500; - order: unset; -} - -span.required:after { - content: " *"; -} - -/* Handle input and textarea fields */ -input:focus ~ .label, -input:placeholder-shown ~ .label, -input[value]:not([value=""]) ~ .label, -textarea:focus ~ .label textarea:placeholder-shown ~ .label, -textarea[value]:not([value=""]) ~ .label { - font-size: 12px; - margin-bottom: var(--Space-x05); -} - -input:disabled ~ .label, -textarea:disabled ~ .label, -:global(.select-container)[data-disabled] .label { - color: var(--Text-Interactive-Disabled); -} - -/* Handle select fields */ -:global(.select-button) .label { - order: unset; -} - -:global(.select-container)[data-open="true"] .label:not(.discreet), -:global(.react-aria-SelectValue):has(:nth-child(2)) .label:not(.discreet) { - font-size: 12px; - margin-bottom: var(--Space-x05); -} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx index b02709fc9..85dbf4653 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx @@ -6,9 +6,9 @@ import { Controller, useFormContext } from "react-hook-form" import { useIntl } from "react-intl" import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Input } from "@scandic-hotels/design-system/Input" import Button from "@/components/TempDesignSystem/Button" -import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel" import Caption from "@/components/TempDesignSystem/Text/Caption" import { passwordValidators } from "@/utils/zod/passwordValidator" @@ -62,7 +62,7 @@ export default function PasswordInput({ } >
- - = { + title: 'Components/Input', + component: ({ isInvalid, ...props }) => ( + + + + ), + argTypes: {}, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + label: 'Label', + name: 'foo', + required: false, + }, +} + +export const Filled: Story = { + args: { + label: 'Label', + name: 'foo', + value: 'Value', + }, +} + +export const Error: Story = { + args: { + label: 'Label', + name: 'foo', + // @ts-expect-error Input does not support this, but wrapping does + isInvalid: true, + }, +} + +export const Disabled: Story = { + args: { + label: 'Label', + name: 'foo', + disabled: true, + }, +} + +export const DisabledFilled: Story = { + args: { + label: 'Label', + name: 'foo', + disabled: true, + value: 'Value', + }, +} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Input/AriaInputWithLabel/index.tsx b/packages/design-system/lib/components/Input/Input.tsx similarity index 59% rename from apps/scandic-web/components/TempDesignSystem/Form/Input/AriaInputWithLabel/index.tsx rename to packages/design-system/lib/components/Input/Input.tsx index b597e211e..adc087dd9 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Input/AriaInputWithLabel/index.tsx +++ b/packages/design-system/lib/components/Input/Input.tsx @@ -1,22 +1,22 @@ -import { cx } from "class-variance-authority" +import { cx } from 'class-variance-authority' import { type ForwardedRef, forwardRef, useId, useImperativeHandle, useRef, -} from "react" -import { Input as AriaInput, Label as AriaLabel } from "react-aria-components" +} from 'react' +import { Input as AriaInput, Label as AriaLabel } from 'react-aria-components' -import Label from "@/components/TempDesignSystem/Form/Label" -import Body from "@/components/TempDesignSystem/Text/Body" +import { Label } from '../Label' -import styles from "./input.module.css" +import styles from './input.module.css' -import type { AriaInputWithLabelProps } from "./input" +import type { InputProps } from './types' +import { Typography } from '../Typography' -const AriaInputWithLabel = forwardRef(function AriaInputWithLabelComponent( - { label, ...props }: AriaInputWithLabelProps, +export const Input = forwardRef(function AriaInputWithLabelComponent( + { label, ...props }: InputProps, forwardedRef: ForwardedRef ) { const ref = useRef(null) @@ -31,17 +31,16 @@ const AriaInputWithLabel = forwardRef(function AriaInputWithLabelComponent( return ( - + - + ) }) - -export default AriaInputWithLabel diff --git a/packages/design-system/lib/components/Input/index.ts b/packages/design-system/lib/components/Input/index.ts new file mode 100644 index 000000000..3188ccc6a --- /dev/null +++ b/packages/design-system/lib/components/Input/index.ts @@ -0,0 +1 @@ +export { Input } from './Input' diff --git a/packages/design-system/lib/components/Input/input.module.css b/packages/design-system/lib/components/Input/input.module.css new file mode 100644 index 000000000..f2521cdb4 --- /dev/null +++ b/packages/design-system/lib/components/Input/input.module.css @@ -0,0 +1,60 @@ +.container { + align-content: center; + background-color: var(--Surface-Primary-Default); + border: 1px solid var(--Border-Interactive-Default); + border-radius: var(--Corner-radius-md); + display: grid; + min-width: 0; /* allow shrinkage */ + height: 60px; + padding: var(--Space-x1) var(--Space-x2); + box-sizing: border-box; + cursor: text; + + &:has(.input:focus) { + outline-offset: -2px; + outline: 2px solid var(--Border-Interactive-Focus); + } + + &:has(.input:disabled) { + background-color: var(--Surface-Primary-Disabled); + border: transparent; + cursor: unset; + } + + &:has(.input[data-invalid='true'], .input[aria-invalid='true']) { + border-color: var(--Border-Interactive-Error); + + &:focus-within { + outline-offset: -2px; + outline: 2px solid var(--Border-Interactive-Error); + } + } +} + +.input { + background: none; + border: none; + color: var(--Text-Default); + height: 1px; + order: 2; + padding: 0; + transition: height 150ms ease; + + &:focus, + &:placeholder-shown, + &[value]:not([value='']) { + height: 18px; + outline: none; + } + + &:disabled { + color: var(--Text-Interactive-Disabled); + } +} + +@media (hover: hover) { + .input:active:not(:disabled) { + height: 18px; + outline: none; + } +} diff --git a/packages/design-system/lib/components/Input/types.ts b/packages/design-system/lib/components/Input/types.ts new file mode 100644 index 000000000..c664747ad --- /dev/null +++ b/packages/design-system/lib/components/Input/types.ts @@ -0,0 +1,6 @@ +import { ComponentProps } from 'react' +import { Input } from 'react-aria-components' + +export interface InputProps extends ComponentProps { + label: string +} diff --git a/packages/design-system/lib/components/Label/Label.stories.tsx b/packages/design-system/lib/components/Label/Label.stories.tsx new file mode 100644 index 000000000..53a217e58 --- /dev/null +++ b/packages/design-system/lib/components/Label/Label.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import { Label } from './Label' + +const meta: Meta = { + title: 'Components/Label', + component: Label, + argTypes: {}, +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + children: 'Label', + required: false, + }, +} + +export const Discreet: Story = { + args: { + children: 'Label', + size: 'discreet', + }, +} + +export const Small: Story = { + args: { + children: 'Label', + size: 'small', + }, +} diff --git a/packages/design-system/lib/components/Label/Label.tsx b/packages/design-system/lib/components/Label/Label.tsx new file mode 100644 index 000000000..9ee9c896e --- /dev/null +++ b/packages/design-system/lib/components/Label/Label.tsx @@ -0,0 +1,13 @@ +import { labelVariants } from './variants' + +import type { LabelProps } from './types' + +export function Label({ children, className, required, size }: LabelProps) { + const classNames = labelVariants({ + className, + size, + required, + }) + + return {children} +} diff --git a/packages/design-system/lib/components/Label/index.ts b/packages/design-system/lib/components/Label/index.ts new file mode 100644 index 000000000..717144f33 --- /dev/null +++ b/packages/design-system/lib/components/Label/index.ts @@ -0,0 +1 @@ +export { Label } from './Label' diff --git a/packages/design-system/lib/components/Label/label.module.css b/packages/design-system/lib/components/Label/label.module.css new file mode 100644 index 000000000..62fc8991c --- /dev/null +++ b/packages/design-system/lib/components/Label/label.module.css @@ -0,0 +1,51 @@ +.label { + composes: Body-Paragraph-mdRegular from '../Typography/typography.module.css'; + transition: font-size 100ms ease; + text-align: left; + color: var(--Text-Interactive-Placeholder); + user-select: none; + + &.small { + display: block; + font-size: 12px; + } + &.regular { + font-size: 16px; + } +} + +.discreet { + composes: Body-Supporting-text-caption-smBold from '../Typography/typography.module.css'; + color: var(--Text-Default); + order: unset; +} + +.required:after { + content: ' *'; +} + +input:focus, +input:placeholder-shown, +input[value]:not([value='']), +textarea:focus, +textarea:placeholder-shown, +textarea[value]:not([value='']) { + & ~ .label { + font-size: 12px; + margin-bottom: var(--Space-x05); + } +} + +input:disabled, +textarea:disabled { + & ~ .label { + color: var(--Text-Interactive-Disabled); + } +} + +@media (hover: hover) { + input:active:not(:disabled) ~ .label { + font-size: 12px; + margin-bottom: var(--Space-x05); + } +} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Label/label.ts b/packages/design-system/lib/components/Label/types.ts similarity index 60% rename from apps/scandic-web/components/TempDesignSystem/Form/Label/label.ts rename to packages/design-system/lib/components/Label/types.ts index 3dbd87e16..f38e947dd 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Label/label.ts +++ b/packages/design-system/lib/components/Label/types.ts @@ -1,6 +1,6 @@ -import type { VariantProps } from "class-variance-authority" +import type { VariantProps } from 'class-variance-authority' -import type { labelVariants } from "./variants" +import type { labelVariants } from './variants' export interface LabelProps extends React.PropsWithChildren>, diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Label/variants.ts b/packages/design-system/lib/components/Label/variants.ts similarity index 69% rename from apps/scandic-web/components/TempDesignSystem/Form/Label/variants.ts rename to packages/design-system/lib/components/Label/variants.ts index e9e354a00..ee28aab93 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Label/variants.ts +++ b/packages/design-system/lib/components/Label/variants.ts @@ -1,6 +1,6 @@ -import { cva } from "class-variance-authority" +import { cva } from 'class-variance-authority' -import styles from "./label.module.css" +import styles from './label.module.css' export const labelVariants = cva(styles.label, { variants: { @@ -11,11 +11,10 @@ export const labelVariants = cva(styles.label, { }, required: { true: styles.required, - false: "", }, }, defaultVariants: { - size: "regular", + size: 'regular', required: false, }, }) diff --git a/packages/design-system/package.json b/packages/design-system/package.json index c163dc0ce..73562a054 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -10,6 +10,8 @@ "./ChipLink": "./dist/components/ChipLink/index.js", "./Chips": "./dist/components/Chips/index.js", "./Divider": "./dist/components/Divider/index.js", + "./Input": "./dist/components/Input/index.js", + "./Label": "./dist/components/Label/index.js", "./Select": "./dist/components/Select/index.js", "./Typography": "./dist/components/Typography/index.js", "./RegularRateCard": "./dist/components/RateCard/Regular/index.js",