From 76a58b90090a5f10937842e502b52cd71b77522c Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 22:41:44 +0100 Subject: [PATCH] Add new FormInput component Signed-off-by: Charles de Dreuille --- .../FormInput/FormInput.stories.tsx | 87 ++++++++++++++++++ .../components/FormInput/FormInput.styles.css | 91 +++++++++++++++++++ .../src/components/FormInput/FormInput.tsx | 48 ++++++++++ .../canon/src/components/FormInput/index.ts | 18 ++++ .../canon/src/components/FormInput/types.ts | 33 +++++++ packages/canon/src/css/components.css | 1 + 6 files changed, 278 insertions(+) create mode 100644 packages/canon/src/components/FormInput/FormInput.stories.tsx create mode 100644 packages/canon/src/components/FormInput/FormInput.styles.css create mode 100644 packages/canon/src/components/FormInput/FormInput.tsx create mode 100644 packages/canon/src/components/FormInput/index.ts create mode 100644 packages/canon/src/components/FormInput/types.ts diff --git a/packages/canon/src/components/FormInput/FormInput.stories.tsx b/packages/canon/src/components/FormInput/FormInput.stories.tsx new file mode 100644 index 0000000000..8210bedb7c --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.stories.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { FormInput } from './FormInput'; +import { Icon } from '../Icon'; +import { Flex } from '../Flex'; + +const meta = { + title: 'Forms/FormInput', + component: FormInput, + argTypes: { + required: { + control: 'boolean', + }, + icon: { + control: 'object', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + name: 'url', + placeholder: 'Enter a URL', + style: { + maxWidth: '300px', + }, + }, +}; + +export const Sizes: Story = { + args: { + ...Default.args, + }, + render: args => ( + + } /> + } /> + + ), +}; + +export const Filled: Story = { + args: { + ...Default.args, + defaultValue: 'https://example.com', + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + disabled: true, + }, +}; + +export const WithIcon: Story = { + args: { + ...Default.args, + placeholder: 'Search...', + icon: , + }, +}; + +export const DisabledWithIcon: Story = { + args: { + ...WithIcon.args, + disabled: true, + }, +}; diff --git a/packages/canon/src/components/FormInput/FormInput.styles.css b/packages/canon/src/components/FormInput/FormInput.styles.css new file mode 100644 index 0000000000..4415247799 --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.styles.css @@ -0,0 +1,91 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.canon-FormInput { + display: flex; + align-items: center; + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); +} + +.canon-FormInputIcon { + margin-right: var(--canon-space-1); + color: var(--canon-fg-primary); + flex-shrink: 0; +} + +.canon-FormInputIcon[data-size='small'], +.canon-FormInputIcon[data-size='small'] svg { + width: 1rem; + height: 1rem; +} + +.canon-FormInputIcon[data-size='medium'], +.canon-FormInputIcon[data-size='medium'] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Input { + border: none; + background: none; + font-size: var(--canon-font-size-3); + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-regular); + color: var(--canon-fg-primary); + transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; + width: 100%; + height: 100%; + cursor: inherit; + padding: 0; +} + +.canon-Input::placeholder { + color: var(--canon-fg-secondary); +} + +.canon-FormInput[data-size='small'] { + height: 2rem; +} + +.canon-FormInput[data-size='medium'] { + height: 2.5rem; +} + +.canon-Input[data-focused] { + outline-color: var(--canon-border-pressed); + outline-width: 0px; +} + +.canon-FormInput:has(> .canon-Input:hover) { + border-color: var(--canon-border-hover); +} + +.canon-FormInput[data-focused] { + border-color: var(--canon-border-pressed); +} + +.canon-FormInput[data-invalid] { + border-color: var(--canon-fg-danger); +} + +.canon-FormInput[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + border: 1px solid var(--canon-border-disabled); +} diff --git a/packages/canon/src/components/FormInput/FormInput.tsx b/packages/canon/src/components/FormInput/FormInput.tsx new file mode 100644 index 0000000000..9e9ca9855f --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { Input } from 'react-aria-components'; +import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import clsx from 'clsx'; + +import type { FormInputProps } from './types'; + +/** @public */ +export const FormInput = forwardRef( + (props: FormInputProps, ref) => { + const { className, icon, size = 'small', ...rest } = props; + + // Get the responsive value for the variant + const responsiveSize = useResponsiveValue(size); + + return ( +
+ {icon && ( + + )} + +
+ ); + }, +); + +FormInput.displayName = 'FormInput'; diff --git a/packages/canon/src/components/FormInput/index.ts b/packages/canon/src/components/FormInput/index.ts new file mode 100644 index 0000000000..29df00ccaf --- /dev/null +++ b/packages/canon/src/components/FormInput/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './TextField'; +export type { TextFieldProps } from './types'; diff --git a/packages/canon/src/components/FormInput/types.ts b/packages/canon/src/components/FormInput/types.ts new file mode 100644 index 0000000000..1f28436259 --- /dev/null +++ b/packages/canon/src/components/FormInput/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputProps } from 'react-aria-components'; +import { ReactNode } from 'react'; +import type { Breakpoint } from '../../types'; + +/** @public */ +export interface FormInputProps extends Omit { + /** + * An icon to render before the input + */ + icon?: ReactNode; + + /** + * The size of the text field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; +} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 609ebea769..1f4c6bad99 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -21,6 +21,7 @@ @import '../components/DataTable/Root/DataTableRoot.styles.css'; @import '../components/DataTable/Pagination/DataTablePagination.styles.css'; @import '../components/Flex/styles.css'; +@import '../components/FormInput/FormInput.styles.css'; @import '../components/Grid/styles.css'; @import '../components/Container/styles.css'; @import '../components/Icon/styles.css';