From f8ca8bc5989b8924bd49ef7df0ef39efed144f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dabiel=20Gonz=C3=A1lez=20Ramos?= Date: Tue, 18 Jul 2023 12:09:21 +0300 Subject: [PATCH] feat(input): add new `bq-input` component (#344) --- package-lock.json | 24 +- packages/bee-q/.storybook/preview.ts | 1 + packages/bee-q/src/components.d.ts | 225 ++++++++++ .../bee-q/src/components/button/readme.md | 2 + packages/bee-q/src/components/icon/readme.md | 2 + .../input/__tests__/bq-input.e2e.ts | 130 ++++++ .../components/input/_storybook/bq-input.mdx | 25 ++ .../input/_storybook/bq-input.stories.tsx | 254 ++++++++++++ .../bee-q/src/components/input/bq-input.tsx | 387 ++++++++++++++++++ .../src/components/input/bq-input.types.ts | 18 + packages/bee-q/src/components/input/readme.md | 81 ++++ .../src/components/input/scss/bq-input.scss | 153 +++++++ .../input/scss/bq-input.variables.scss | 59 +++ .../src/components/tooltip/bq-tooltip.tsx | 2 +- .../tools/angular-value-accessor-config.ts | 2 +- 15 files changed, 1362 insertions(+), 3 deletions(-) create mode 100644 packages/bee-q/src/components/input/__tests__/bq-input.e2e.ts create mode 100644 packages/bee-q/src/components/input/_storybook/bq-input.mdx create mode 100644 packages/bee-q/src/components/input/_storybook/bq-input.stories.tsx create mode 100644 packages/bee-q/src/components/input/bq-input.tsx create mode 100644 packages/bee-q/src/components/input/bq-input.types.ts create mode 100644 packages/bee-q/src/components/input/readme.md create mode 100644 packages/bee-q/src/components/input/scss/bq-input.scss create mode 100644 packages/bee-q/src/components/input/scss/bq-input.variables.scss diff --git a/package-lock.json b/package-lock.json index bf486829a..b42b33ee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9962,6 +9962,10 @@ "typescript": "^3 || ^4" } }, + "node_modules/@phosphor-icons/core": { + "resolved": "packages/bee-q-icons/temp/core-main", + "link": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -40486,10 +40490,28 @@ "node": ">=4.2.0" } }, + "packages/bee-q-icons/node_modules/@types/node": { + "version": "18.16.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", + "dev": true + }, + "packages/bee-q-icons/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "packages/bee-q-icons/temp/core-main": { "name": "@phosphor-icons/core", "version": "2.0.2", - "extraneous": true, "license": "MIT", "devDependencies": { "@types/node": "^18", diff --git a/packages/bee-q/.storybook/preview.ts b/packages/bee-q/.storybook/preview.ts index 1eaad201d..24024349e 100644 --- a/packages/bee-q/.storybook/preview.ts +++ b/packages/bee-q/.storybook/preview.ts @@ -29,5 +29,6 @@ export const parameters = { printWidth: 80, useTabs: false, }, + root: '#root-inner', }, }; diff --git a/packages/bee-q/src/components.d.ts b/packages/bee-q/src/components.d.ts index caeb3683d..f13a94a15 100644 --- a/packages/bee-q/src/components.d.ts +++ b/packages/bee-q/src/components.d.ts @@ -11,6 +11,7 @@ import { TButtonAppearance, TButtonSize, TButtonType, TButtonVariant } from "./c import { TDialogFooterAppearance, TDialogSize } from "./components/dialog/bq-dialog.types"; import { TDividerOrientation, TDividerStrokeLinecap, TDividerTitleAlignment } from "./components/divider/bq-divider.types"; import { TIconWeight } from "./components/icon/bq-icon.types"; +import { TInputType, TInputValidation, TInputValue } from "./components/input/bq-input.types"; import { TNotificationType } from "./components/notification/bq-notification.types"; import { TRadioGroupOrientation } from "./components/radio-group/bq-radio-group.types"; import { TSideMenuAppearance, TSideMenuSize } from "./components/side-menu/bq-side-menu.types"; @@ -27,6 +28,7 @@ export { TButtonAppearance, TButtonSize, TButtonType, TButtonVariant } from "./c export { TDialogFooterAppearance, TDialogSize } from "./components/dialog/bq-dialog.types"; export { TDividerOrientation, TDividerStrokeLinecap, TDividerTitleAlignment } from "./components/divider/bq-divider.types"; export { TIconWeight } from "./components/icon/bq-icon.types"; +export { TInputType, TInputValidation, TInputValue } from "./components/input/bq-input.types"; export { TNotificationType } from "./components/notification/bq-notification.types"; export { TRadioGroupOrientation } from "./components/radio-group/bq-radio-group.types"; export { TSideMenuAppearance, TSideMenuSize } from "./components/side-menu/bq-side-menu.types"; @@ -305,6 +307,101 @@ export namespace Components { */ "weight"?: TIconWeight; } + interface BqInput { + /** + * Controls whether or not the input field should be capitalized and how. Possible values are 'off', 'none', 'on', 'sentences', 'words', and 'characters'. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize + */ + "autocapitalize": string; + /** + * Specifies whether or not the input field should have autocomplete enabled. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values + */ + "autocomplete": string; + /** + * Controls whether or not the input field should have autocorrect enabled. Possible values are 'on' and 'off'. + */ + "autocorrect": 'on' | 'off'; + /** + * If true, the input will be focused on component render + */ + "autofocus": boolean; + /** + * The clear button aria label + */ + "clearButtonLabel"?: string; + /** + * The amount of time, in milliseconds, to wait before emitting the `bqInput` event after the input value changes. A value of 0 means no debouncing will occur. + */ + "debounceTime"?: number; + /** + * If true, the clear button won't be displayed + */ + "disableClear"?: boolean; + /** + * Indicates whether the input is disabled or not. If `true`, the input is disabled and cannot be interacted with. + */ + "disabled"?: boolean; + /** + * The ID of the form that the input field belongs to. + */ + "form"?: string; + /** + * The inputmode attribute specifies what kind of input mechanism would be most helpful for users entering content into the input field. This allows a browser to display an appropriate virtual keyboard while editing. Possible values are 'none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url', and 'date'. + */ + "inputmode"?: string; + /** + * The maximum value that the input field can accept. Only applies to date and number input types. + */ + "max"?: number | string; + /** + * The maximum number of characters that the input field can accept. + */ + "maxlength": number; + /** + * The minimum value that the input field can accept. Only applies to date and number input types. + */ + "min"?: number | string; + /** + * The minimum number of characters that the input field can accept. + */ + "minlength": number; + /** + * The input field name. + */ + "name": string; + /** + * Specifies a regular expression the form control's value should match. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern + */ + "pattern"?: string; + /** + * The input placeholder text value + */ + "placeholder"?: string; + /** + * If true, the input field cannot be modified. + */ + "readonly"?: boolean; + /** + * Indicates whether or not the input field is required to be filled out before submitting the form. + */ + "required"?: boolean; + /** + * A number that specifies the granularity that the value must adhere to. Valid for date, month, week, time, datetime-local, number, and range. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step + */ + "step": number | 'any'; + /** + * The type attribute specifies the type of input field to display. Possible values are 'text', 'password', 'email', 'number', 'tel', 'search', 'url', and more. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types + */ + "type": TInputType; + /** + * The validation status of the input. + * @remarks This property is used to indicate the validation status of the input. It can be set to one of the following values: - `'none'`: No validation status is set. - `'error'`: The input has a validation error. - `'warning'`: The input has a validation warning. - `'success'`: The input has passed validation. + */ + "validationStatus": TInputValidation; + /** + * The input value, it can be used to reset the input to a previous value + */ + "value": TInputValue; + } interface BqNotification { /** * If true, the notification will automatically hide after the specified amount of time @@ -712,6 +809,10 @@ export interface BqIconCustomEvent extends CustomEvent { detail: T; target: HTMLBqIconElement; } +export interface BqInputCustomEvent extends CustomEvent { + detail: T; + target: HTMLBqInputElement; +} export interface BqNotificationCustomEvent extends CustomEvent { detail: T; target: HTMLBqNotificationElement; @@ -816,6 +917,12 @@ declare global { prototype: HTMLBqIconElement; new (): HTMLBqIconElement; }; + interface HTMLBqInputElement extends Components.BqInput, HTMLStencilElement { + } + var HTMLBqInputElement: { + prototype: HTMLBqInputElement; + new (): HTMLBqInputElement; + }; interface HTMLBqNotificationElement extends Components.BqNotification, HTMLStencilElement { } var HTMLBqNotificationElement: { @@ -911,6 +1018,7 @@ declare global { "bq-dialog": HTMLBqDialogElement; "bq-divider": HTMLBqDividerElement; "bq-icon": HTMLBqIconElement; + "bq-input": HTMLBqInputElement; "bq-notification": HTMLBqNotificationElement; "bq-radio": HTMLBqRadioElement; "bq-radio-group": HTMLBqRadioGroupElement; @@ -1242,6 +1350,121 @@ declare namespace LocalJSX { */ "weight"?: TIconWeight; } + interface BqInput { + /** + * Controls whether or not the input field should be capitalized and how. Possible values are 'off', 'none', 'on', 'sentences', 'words', and 'characters'. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize + */ + "autocapitalize"?: string; + /** + * Specifies whether or not the input field should have autocomplete enabled. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values + */ + "autocomplete"?: string; + /** + * Controls whether or not the input field should have autocorrect enabled. Possible values are 'on' and 'off'. + */ + "autocorrect"?: 'on' | 'off'; + /** + * If true, the input will be focused on component render + */ + "autofocus"?: boolean; + /** + * The clear button aria label + */ + "clearButtonLabel"?: string; + /** + * The amount of time, in milliseconds, to wait before emitting the `bqInput` event after the input value changes. A value of 0 means no debouncing will occur. + */ + "debounceTime"?: number; + /** + * If true, the clear button won't be displayed + */ + "disableClear"?: boolean; + /** + * Indicates whether the input is disabled or not. If `true`, the input is disabled and cannot be interacted with. + */ + "disabled"?: boolean; + /** + * The ID of the form that the input field belongs to. + */ + "form"?: string; + /** + * The inputmode attribute specifies what kind of input mechanism would be most helpful for users entering content into the input field. This allows a browser to display an appropriate virtual keyboard while editing. Possible values are 'none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url', and 'date'. + */ + "inputmode"?: string; + /** + * The maximum value that the input field can accept. Only applies to date and number input types. + */ + "max"?: number | string; + /** + * The maximum number of characters that the input field can accept. + */ + "maxlength"?: number; + /** + * The minimum value that the input field can accept. Only applies to date and number input types. + */ + "min"?: number | string; + /** + * The minimum number of characters that the input field can accept. + */ + "minlength"?: number; + /** + * The input field name. + */ + "name": string; + /** + * Callback handler emitted when the input loses focus + */ + "onBqBlur"?: (event: BqInputCustomEvent) => void; + /** + * Callback handler emitted when the input value has changed and the input loses focus. This handler is called whenever the user finishes typing or pasting text into the input field and then clicks outside of the input field. + */ + "onBqChange"?: (event: BqInputCustomEvent<{ value: string | number | string[]; el: HTMLBqInputElement }>) => void; + /** + * Callback handler emitted when the input value has been cleared + */ + "onBqClear"?: (event: BqInputCustomEvent) => void; + /** + * Callback handler emitted when the input has received focus + */ + "onBqFocus"?: (event: BqInputCustomEvent) => void; + /** + * Callback handler emitted when the input value changes. This handler is called whenever the user types or pastes text into the input field. + */ + "onBqInput"?: (event: BqInputCustomEvent<{ value: string | number | string[]; el: HTMLBqInputElement }>) => void; + /** + * Specifies a regular expression the form control's value should match. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern + */ + "pattern"?: string; + /** + * The input placeholder text value + */ + "placeholder"?: string; + /** + * If true, the input field cannot be modified. + */ + "readonly"?: boolean; + /** + * Indicates whether or not the input field is required to be filled out before submitting the form. + */ + "required"?: boolean; + /** + * A number that specifies the granularity that the value must adhere to. Valid for date, month, week, time, datetime-local, number, and range. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step + */ + "step"?: number | 'any'; + /** + * The type attribute specifies the type of input field to display. Possible values are 'text', 'password', 'email', 'number', 'tel', 'search', 'url', and more. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types + */ + "type"?: TInputType; + /** + * The validation status of the input. + * @remarks This property is used to indicate the validation status of the input. It can be set to one of the following values: - `'none'`: No validation status is set. - `'error'`: The input has a validation error. - `'warning'`: The input has a validation warning. - `'success'`: The input has passed validation. + */ + "validationStatus"?: TInputValidation; + /** + * The input value, it can be used to reset the input to a previous value + */ + "value"?: TInputValue; + } interface BqNotification { /** * If true, the notification will automatically hide after the specified amount of time @@ -1658,6 +1881,7 @@ declare namespace LocalJSX { "bq-dialog": BqDialog; "bq-divider": BqDivider; "bq-icon": BqIcon; + "bq-input": BqInput; "bq-notification": BqNotification; "bq-radio": BqRadio; "bq-radio-group": BqRadioGroup; @@ -1695,6 +1919,7 @@ declare module "@stencil/core" { * Icons are simplified images that graphically explain the meaning of an object on the screen. */ "bq-icon": LocalJSX.BqIcon & JSXBase.HTMLAttributes; + "bq-input": LocalJSX.BqInput & JSXBase.HTMLAttributes; "bq-notification": LocalJSX.BqNotification & JSXBase.HTMLAttributes; "bq-radio": LocalJSX.BqRadio & JSXBase.HTMLAttributes; "bq-radio-group": LocalJSX.BqRadioGroup & JSXBase.HTMLAttributes; diff --git a/packages/bee-q/src/components/button/readme.md b/packages/bee-q/src/components/button/readme.md index 28bf945ac..afe3a9605 100644 --- a/packages/bee-q/src/components/button/readme.md +++ b/packages/bee-q/src/components/button/readme.md @@ -50,6 +50,7 @@ Buttons are designed for users to take action on a page or a screen. ### Used by - [bq-dialog](../dialog) + - [bq-input](../input) - [bq-notification](../notification) ### Depends on @@ -61,6 +62,7 @@ Buttons are designed for users to take action on a page or a screen. graph TD; bq-button --> bq-icon bq-dialog --> bq-button + bq-input --> bq-button bq-notification --> bq-button style bq-button fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/bee-q/src/components/icon/readme.md b/packages/bee-q/src/components/icon/readme.md index 41cedf6a2..f873a0046 100644 --- a/packages/bee-q/src/components/icon/readme.md +++ b/packages/bee-q/src/components/icon/readme.md @@ -38,6 +38,7 @@ Icons are simplified images that graphically explain the meaning of an object on - [bq-button](../button) - [bq-dialog](../dialog) + - [bq-input](../input) - [bq-notification](../notification) - [bq-switch](../switch) - [bq-toast](../toast) @@ -47,6 +48,7 @@ Icons are simplified images that graphically explain the meaning of an object on graph TD; bq-button --> bq-icon bq-dialog --> bq-icon + bq-input --> bq-icon bq-notification --> bq-icon bq-switch --> bq-icon bq-toast --> bq-icon diff --git a/packages/bee-q/src/components/input/__tests__/bq-input.e2e.ts b/packages/bee-q/src/components/input/__tests__/bq-input.e2e.ts new file mode 100644 index 000000000..6fb00a987 --- /dev/null +++ b/packages/bee-q/src/components/input/__tests__/bq-input.e2e.ts @@ -0,0 +1,130 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('bq-input', () => { + it('should render', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const bqInputElem = await page.find('bq-input'); + expect(bqInputElem).toHaveClass('hydrated'); + }); + + it('should have shadow root', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const bqInputElem = await page.find('bq-input'); + expect(bqInputElem.shadowRoot).not.toBeNull(); + }); + + it('should render with prefix icon', async () => { + const page = await newE2EPage(); + await page.setContent(` + + + + `); + + const prefixContainerElem = await page.find('bq-input >>> .bq-input--control__prefix'); + expect(prefixContainerElem).not.toHaveClass('hidden'); + }); + + it('should render with suffix icon', async () => { + const page = await newE2EPage(); + await page.setContent(` + + + + `); + + const suffixContainerElem = await page.find('bq-input >>> .bq-input--control__suffix'); + expect(suffixContainerElem).not.toHaveClass('hidden'); + }); + + it('should render with label content', async () => { + const page = await newE2EPage(); + await page.setContent(` + + + + `); + + const labelContainerElem = await page.find('bq-input >>> .bq-input--label'); + expect(labelContainerElem).not.toHaveClass('hidden'); + }); + + it('should render with helper content', async () => { + const page = await newE2EPage(); + await page.setContent(` + + Helper text + + `); + + const helperContainerElem = await page.find('bq-input >>> .bq-input--helper-text'); + expect(helperContainerElem).not.toHaveClass('hidden'); + }); + + it('should write and emit change event', async () => { + const inputValue = 'Hello World!'; + const page = await newE2EPage(); + await page.setContent(` + + `); + + const bqChange = await page.spyOnEvent('bqChange'); + + const bqInputElem = await page.find('bq-input'); + const nativeInputElem = await page.find('bq-input >>> .bq-input--control__input'); + + await nativeInputElem.type(inputValue); + await page.$eval('bq-input >>> .bq-input--control__input', (e: HTMLInputElement) => { + e.blur(); + }); + await page.waitForChanges(); + + expect(await bqInputElem.getProperty('value')).toBe(inputValue); + expect(bqChange).toHaveReceivedEventTimes(1); + }); + + it('should write and emit input event', async () => { + const inputValue = 'Hello World!'; + const page = await newE2EPage(); + await page.setContent(` + + `); + + const bqInput = await page.spyOnEvent('bqInput'); + + const bqInputElem = await page.find('bq-input'); + const nativeInputElem = await page.find('bq-input >>> .bq-input--control__input'); + + await nativeInputElem.type(inputValue); + await page.waitForChanges(); + + expect(await bqInputElem.getProperty('value')).toBe(inputValue); + expect(bqInput).toHaveReceivedEventTimes(inputValue.length); + }); + + it('should clear the value and emit clear event', async () => { + const page = await newE2EPage(); + await page.setContent(` + + `); + + const bqClear = await page.spyOnEvent('bqClear'); + + const bqInputElem = await page.find('bq-input'); + const nativeInputElem = await page.find('bq-input >>> .bq-input--control__input'); + expect(await nativeInputElem.getProperty('value')).toBe('Hello World!'); + + await nativeInputElem.focus(); + await page.waitForChanges(); + + const clearBtnElem = await page.find('bq-input >>> .bq-input--control__clear'); + await clearBtnElem.click(); + + expect(bqClear).toHaveReceivedEventTimes(1); + expect(await bqInputElem.getProperty('value')).toEqual(''); + }); +}); diff --git a/packages/bee-q/src/components/input/_storybook/bq-input.mdx b/packages/bee-q/src/components/input/_storybook/bq-input.mdx new file mode 100644 index 000000000..2f5c199ba --- /dev/null +++ b/packages/bee-q/src/components/input/_storybook/bq-input.mdx @@ -0,0 +1,25 @@ +import { ArgTypes, Title, Subtitle } from '@storybook/addon-docs'; + +Input + +The Input component is a fundamental user interface element that allows users to input data by typing it into a text field. It is commonly used in web and mobile applications for various purposes, such as collecting user information, search inputs, and login forms. + +Usage + +- Do use the Input component to provide a visual and interactive way for users to input data. +- Do make the Input component accessible by providing appropriate labels and helpful hints. +- Do provide appropriate input validation to enhance data integrity and prevent errors. +- Do test the Input component across different browsers and devices for compatibility and responsiveness. + +👍 When to use + +The Input component should be used when you need to collect user input, such as: + +- Capturing personal information in registration or signup forms. +- Implementing search functionality for filtering data. +- Entering login credentials. +- Allowing users to submit comments, messages, or feedback. + +Properties + + diff --git a/packages/bee-q/src/components/input/_storybook/bq-input.stories.tsx b/packages/bee-q/src/components/input/_storybook/bq-input.stories.tsx new file mode 100644 index 000000000..bdd60100a --- /dev/null +++ b/packages/bee-q/src/components/input/_storybook/bq-input.stories.tsx @@ -0,0 +1,254 @@ +import type { Args, Meta, StoryObj } from '@storybook/web-components'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { html, nothing } from 'lit-html'; + +import mdx from './bq-input.mdx'; +import { INPUT_TYPE, INPUT_VALIDATION } from '../bq-input.types'; + +const meta: Meta = { + title: 'Components/Input', + component: 'bq-input', + parameters: { + docs: { + page: mdx, + }, + }, + argTypes: { + autocapitalize: { control: 'text' }, + autocomplete: { control: 'text' }, + autocorrect: { control: 'inline-radio', options: ['on', 'off'] }, + autofocus: { control: 'boolean' }, + 'clear-button-label': { control: 'text' }, + 'debounce-time': { control: 'number' }, + 'disable-clear': { control: 'boolean' }, + disabled: { control: 'boolean' }, + max: { control: 'text' }, + maxlength: { control: 'number' }, + min: { control: 'text' }, + minlength: { control: 'number' }, + name: { control: 'text' }, + pattern: { control: 'text' }, + placeholder: { control: 'text' }, + readonly: { control: 'boolean' }, + required: { control: 'boolean' }, + step: { control: 'text' }, + type: { control: 'select', options: [...INPUT_TYPE] }, + 'validation-status': { control: 'select', options: [...INPUT_VALIDATION] }, + value: { control: 'text' }, + // Events + bqBlur: { action: 'bqBlur' }, + bqChange: { action: 'bqChange' }, + bqClear: { action: 'bqClear' }, + bqFocus: { action: 'bqFocus' }, + bqInput: { action: 'bqInput' }, + // Not part of the public API, so we don't want to expose it in the docs + noLabel: { control: 'bolean', table: { disable: true } }, + hasLabelTooltip: { control: 'bolean', table: { disable: true } }, + noHelperText: { control: 'bolean', table: { disable: true } }, + optionalLabel: { control: 'bolean', table: { disable: true } }, + prefix: { control: 'bolean', table: { disable: true } }, + suffix: { control: 'bolean', table: { disable: true } }, + }, + args: { + autocapitalize: 'off', + autocomplete: 'off', + autocorrect: 'off', + autofocus: false, + 'clear-button-label': 'Clear value', + 'disable-clear': false, + disabled: false, + 'debounce-time': 0, + form: undefined, + inputmode: 'text', + max: undefined, + maxlength: undefined, + min: undefined, + minlength: undefined, + name: 'input', + pattern: undefined, + readonly: false, + required: false, + step: undefined, + type: 'text', + placeholder: 'Placeholder', + 'validation-status': 'none', + value: undefined, + }, +}; +export default meta; + +type Story = StoryObj; + +const Template = (args: Args) => { + const tooltipTemplate = args.hasLabelTooltip + ? html` + + + You can provide more context detail by adding a tooltip to the label. + + ` + : nothing; + const labelTemplate = html` + + `; + const label = !args.optionalLabel + ? labelTemplate + : html` +
+ ${labelTemplate} + Optional +
+ `; + + return html` + + ${!args.noLabel ? label : nothing} + ${args.prefix ? html`` : nothing} + ${args.suffix ? html`` : nothing} + ${!args.noHelperText + ? html` + + + Helper text + + ` + : nothing} + + `; +}; + +export const Default: Story = { + render: Template, +}; + +export const Value: Story = { + render: Template, + args: { + value: 'Hello World!', + }, +}; + +export const Prefix: Story = { + render: Template, + args: { + prefix: true, + }, +}; + +export const Suffix: Story = { + render: Template, + args: { + suffix: true, + }, +}; + +export const PrefixAndSuffix: Story = { + name: 'Prefix and Suffix', + render: Template, + args: { + prefix: true, + suffix: true, + }, +}; + +export const Disabled: Story = { + render: Template, + args: { + disabled: true, + prefix: true, + suffix: true, + }, +}; + +export const ValidationStatus: Story = { + name: 'Validation', + render: (args) => html` +
+ + ${Template({ ...args, 'validation-status': 'error' })} + + ${Template({ ...args, 'validation-status': 'success' })} + + ${Template({ ...args, 'validation-status': 'warning' })} +
+ `, + args: { + prefix: true, + suffix: true, + }, +}; + +export const Optional: Story = { + name: 'Label with "Optional"', + render: Template, + args: { + optionalLabel: true, + prefix: true, + suffix: true, + }, +}; + +export const Tooltip: Story = { + name: 'Label with "Info tooltip"', + render: Template, + args: { + hasLabelTooltip: true, + optionalLabel: true, + prefix: true, + suffix: true, + }, + parameters: { + layout: 'centered', + }, +}; + +export const NoLabel: Story = { + name: 'With no Label', + render: Template, + args: { + noLabel: true, + prefix: true, + suffix: true, + }, +}; + +export const NoHelperText: Story = { + name: 'With no Helper Text', + render: Template, + args: { + noHelperText: true, + prefix: true, + suffix: true, + }, +}; diff --git a/packages/bee-q/src/components/input/bq-input.tsx b/packages/bee-q/src/components/input/bq-input.tsx new file mode 100644 index 000000000..9641d18e0 --- /dev/null +++ b/packages/bee-q/src/components/input/bq-input.tsx @@ -0,0 +1,387 @@ +import { Component, Element, Event, EventEmitter, h, Prop, State, Watch } from '@stencil/core'; + +import { TInputType, TInputValidation, TInputValue } from './bq-input.types'; +import { debounce, hasSlotContent, isDefined, isHTMLElement, TDebounce } from '../../shared/utils'; + +/** + * @part base - The component's base wrapper. + * @part button - The native HTML button used under the hood in the clear button. + * @part clear-btn - The clear button. + * @part control - The input control wrapper. + * @part helper-text - The helper text slot container. + * @part label - The label slot container. + * @part input - The native HTML input element used under the hood. + * @part prefix - The prefix slot container. + * @part suffix - The suffix slot container. + */ +@Component({ + tag: 'bq-input', + styleUrl: './scss/bq-input.scss', + shadow: { + delegatesFocus: true, + }, +}) +export class BqInput { + // Own Properties + // ==================== + + private helperTextElem?: HTMLElement; + private inputElem?: HTMLInputElement; + private labelElem?: HTMLElement; + private prefixElem?: HTMLElement; + private suffixElem?: HTMLElement; + + private debounceBqInput: TDebounce; + + // Reference to host HTML element + // =================================== + + @Element() el!: HTMLBqInputElement; + + // State() variables + // Inlined decorator, alphabetical order + // ======================================= + + @State() hasHelperText = false; + @State() hasLabel = false; + @State() hasPrefix = false; + @State() hasSuffix = false; + @State() hasValue = false; + + // Public Property API + // ======================== + + /** + * Controls whether or not the input field should be capitalized and how. + * Possible values are 'off', 'none', 'on', 'sentences', 'words', and 'characters'. + * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize + */ + @Prop({ reflect: true }) autocapitalize: string = 'off'; + + /** + * Specifies whether or not the input field should have autocomplete enabled. + * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values + */ + @Prop({ reflect: true }) autocomplete: string = 'off'; + + /** + * Controls whether or not the input field should have autocorrect enabled. + * Possible values are 'on' and 'off'. + */ + @Prop({ reflect: true }) autocorrect: 'on' | 'off' = 'off'; + + /** If true, the input will be focused on component render */ + @Prop({ reflect: true }) autofocus: boolean; + + /** The clear button aria label */ + @Prop({ reflect: true }) clearButtonLabel? = 'Clear value'; + + /** + * The amount of time, in milliseconds, to wait before emitting the `bqInput` event after the input value changes. + * A value of 0 means no debouncing will occur. + */ + @Prop({ reflect: true, mutable: true }) debounceTime? = 0; + + /** + * Indicates whether the input is disabled or not. + * If `true`, the input is disabled and cannot be interacted with. + */ + @Prop({ mutable: true }) disabled?: boolean = false; + + /** If true, the clear button won't be displayed */ + @Prop({ reflect: true }) disableClear? = false; + + /** The ID of the form that the input field belongs to. */ + @Prop({ reflect: true }) form?: string; + + /** + * The inputmode attribute specifies what kind of input mechanism would be most helpful for users entering content into the input field. + * This allows a browser to display an appropriate virtual keyboard while editing. + * Possible values are 'none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url', and 'date'. + */ + @Prop() inputmode?: string; + + /** + * The maximum value that the input field can accept. + * Only applies to date and number input types. + */ + @Prop({ reflect: true }) max?: number | string; + + /** The maximum number of characters that the input field can accept. */ + @Prop({ reflect: true }) maxlength: number; + + /** + * The minimum value that the input field can accept. + * Only applies to date and number input types. + */ + @Prop({ reflect: true }) min?: number | string; + + /** The minimum number of characters that the input field can accept. */ + @Prop({ reflect: true }) minlength: number; + + /** The input field name. */ + @Prop({ reflect: true }) name!: string; + + /** + * Specifies a regular expression the form control's value should match. + * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern + */ + @Prop({ reflect: true }) pattern?: string; + + /** The input placeholder text value */ + @Prop({ reflect: true }) placeholder?: string; + + /** If true, the input field cannot be modified. */ + @Prop({ reflect: true }) readonly?: boolean; + + /** Indicates whether or not the input field is required to be filled out before submitting the form. */ + @Prop({ reflect: true }) required?: boolean; + + /** + * A number that specifies the granularity that the value must adhere to. + * Valid for date, month, week, time, datetime-local, number, and range. + * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step + */ + @Prop({ reflect: true }) step: number | 'any'; + + /** + * The type attribute specifies the type of input field to display. + * Possible values are 'text', 'password', 'email', 'number', 'tel', 'search', 'url', and more. + * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types + */ + @Prop({ reflect: true }) type: TInputType = 'text'; + + /** + * The validation status of the input. + * + * @remarks + * This property is used to indicate the validation status of the input. It can be set to one of the following values: + * - `'none'`: No validation status is set. + * - `'error'`: The input has a validation error. + * - `'warning'`: The input has a validation warning. + * - `'success'`: The input has passed validation. + */ + @Prop({ reflect: true }) validationStatus: TInputValidation = 'none'; + + /** The input value, it can be used to reset the input to a previous value */ + @Prop({ reflect: true, mutable: true }) value: TInputValue; + + // Prop lifecycle events + // ======================= + + @Watch('value') + handleValueChange() { + if (Array.isArray(this.value)) { + this.hasValue = this.value.some((val) => val.length > 0); + return; + } + + this.hasValue = isDefined(this.value); + } + + // Events section + // Requires JSDocs for public API documentation + // ============================================== + + /** Callback handler emitted when the input loses focus */ + @Event() bqBlur!: EventEmitter; + + /** + * Callback handler emitted when the input value has changed and the input loses focus. + * This handler is called whenever the user finishes typing or pasting text into the input field and then clicks outside of the input field. + */ + @Event() bqChange!: EventEmitter<{ value: string | number | string[]; el: HTMLBqInputElement }>; + + /** Callback handler emitted when the input value has been cleared */ + @Event() bqClear!: EventEmitter; + + /** Callback handler emitted when the input has received focus */ + @Event() bqFocus!: EventEmitter; + + /** + * Callback handler emitted when the input value changes. + * This handler is called whenever the user types or pastes text into the input field. + */ + @Event() bqInput!: EventEmitter<{ value: string | number | string[]; el: HTMLBqInputElement }>; + + // Component lifecycle events + // Ordered by their natural call order + // ===================================== + + componentDidLoad() { + this.handleValueChange(); + } + + // Listeners + // ============== + + // Public methods API + // These methods are exposed on the host element. + // Always use two lines. + // Public Methods must be async. + // Requires JSDocs for public API documentation. + // =============================================== + + // Local methods + // Internal business logic. + // These methods cannot be called from the host element. + // ======================================================= + + private handleBlur = () => { + this.bqBlur.emit(this.el); + }; + + private handleFocus = () => { + this.bqFocus.emit(this.el); + }; + + private handleInput = (ev: Event) => { + this.debounceBqInput?.cancel(); + + if (!isHTMLElement(ev.target, 'input')) return; + this.value = ev.target.value; + + this.debounceBqInput = debounce(() => { + this.bqInput.emit({ value: this.value, el: this.el }); + }, this.debounceTime); + this.debounceBqInput(); + }; + + private handleChange = (ev: Event) => { + if (!isHTMLElement(ev.target, 'input')) return; + this.value = ev.target.value; + + this.bqChange.emit({ value: this.value, el: this.el }); + }; + + private handleClearClick = (ev: CustomEvent) => { + this.inputElem.value = ''; + this.value = this.inputElem.value; + + this.bqClear.emit(this.el); + this.bqChange.emit({ value: this.value, el: this.el }); + this.inputElem.focus(); + + ev.stopPropagation(); + }; + + private handleLabelSlotChange = () => { + this.hasLabel = hasSlotContent(this.labelElem); + }; + + private handlePrefixSlotChange = () => { + this.hasPrefix = hasSlotContent(this.prefixElem); + }; + + private handleSuffixSlotChange = () => { + this.hasSuffix = hasSlotContent(this.suffixElem); + }; + + private handleHelperTextSlotChange = () => { + this.hasHelperText = hasSlotContent(this.helperTextElem); + }; + + // render() function + // Always the last one in the class. + // =================================== + + render() { + return ( +
+ {/* Label */} + + {/* Input control group */} +
+ {/* Prefix */} + (this.prefixElem = spanElem)} + part="prefix" + > + + + {/* HTML Input */} + (this.inputElem = inputElem)} + readOnly={this.readonly} + required={this.required} + step={this.step} + type={this.type} + value={this.value} + part="input" + // Events + onBlur={this.handleBlur} + onChange={this.handleChange} + onFocus={this.handleFocus} + onInput={this.handleInput} + /> + {/* Clear Button */} + {!this.disableClear && this.hasValue && ( + // The clear button will be visible as long as the input has a value + // and the parent group is hovered or has focus-within + + )} + {/* Suffix */} + (this.suffixElem = spanElem)} + part="suffix" + > + + +
+ {/* Helper text */} +
(this.helperTextElem = divElem)} + part="helper-text" + > + +
+
+ ); + } +} diff --git a/packages/bee-q/src/components/input/bq-input.types.ts b/packages/bee-q/src/components/input/bq-input.types.ts new file mode 100644 index 000000000..6e9e6345e --- /dev/null +++ b/packages/bee-q/src/components/input/bq-input.types.ts @@ -0,0 +1,18 @@ +export const INPUT_TYPE = [ + 'date', + 'datetime-local', + 'email', + 'number', + 'password', + 'search', + 'tel', + 'text', + 'time', + 'url', +] as const; +export type TInputType = (typeof INPUT_TYPE)[number]; + +export const INPUT_VALIDATION = ['error', 'none', 'success', 'warning'] as const; +export type TInputValidation = (typeof INPUT_VALIDATION)[number]; + +export type TInputValue = string | number | string[]; diff --git a/packages/bee-q/src/components/input/readme.md b/packages/bee-q/src/components/input/readme.md new file mode 100644 index 000000000..670af1a02 --- /dev/null +++ b/packages/bee-q/src/components/input/readme.md @@ -0,0 +1,81 @@ +# bq-input + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------- | +| `autocapitalize` | `autocapitalize` | Controls whether or not the input field should be capitalized and how. Possible values are 'off', 'none', 'on', 'sentences', 'words', and 'characters'. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize | `string` | `'off'` | +| `autocomplete` | `autocomplete` | Specifies whether or not the input field should have autocomplete enabled. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values | `string` | `'off'` | +| `autocorrect` | `autocorrect` | Controls whether or not the input field should have autocorrect enabled. Possible values are 'on' and 'off'. | `"off" \| "on"` | `'off'` | +| `autofocus` | `autofocus` | If true, the input will be focused on component render | `boolean` | `undefined` | +| `clearButtonLabel` | `clear-button-label` | The clear button aria label | `string` | `'Clear value'` | +| `debounceTime` | `debounce-time` | The amount of time, in milliseconds, to wait before emitting the `bqInput` event after the input value changes. A value of 0 means no debouncing will occur. | `number` | `0` | +| `disableClear` | `disable-clear` | If true, the clear button won't be displayed | `boolean` | `false` | +| `disabled` | `disabled` | Indicates whether the input is disabled or not. If `true`, the input is disabled and cannot be interacted with. | `boolean` | `false` | +| `form` | `form` | The ID of the form that the input field belongs to. | `string` | `undefined` | +| `inputmode` | `inputmode` | The inputmode attribute specifies what kind of input mechanism would be most helpful for users entering content into the input field. This allows a browser to display an appropriate virtual keyboard while editing. Possible values are 'none', 'text', 'decimal', 'numeric', 'tel', 'search', 'email', 'url', and 'date'. | `string` | `undefined` | +| `max` | `max` | The maximum value that the input field can accept. Only applies to date and number input types. | `number \| string` | `undefined` | +| `maxlength` | `maxlength` | The maximum number of characters that the input field can accept. | `number` | `undefined` | +| `min` | `min` | The minimum value that the input field can accept. Only applies to date and number input types. | `number \| string` | `undefined` | +| `minlength` | `minlength` | The minimum number of characters that the input field can accept. | `number` | `undefined` | +| `name` _(required)_ | `name` | The input field name. | `string` | `undefined` | +| `pattern` | `pattern` | Specifies a regular expression the form control's value should match. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern | `string` | `undefined` | +| `placeholder` | `placeholder` | The input placeholder text value | `string` | `undefined` | +| `readonly` | `readonly` | If true, the input field cannot be modified. | `boolean` | `undefined` | +| `required` | `required` | Indicates whether or not the input field is required to be filled out before submitting the form. | `boolean` | `undefined` | +| `step` | `step` | A number that specifies the granularity that the value must adhere to. Valid for date, month, week, time, datetime-local, number, and range. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step | `"any" \| number` | `undefined` | +| `type` | `type` | The type attribute specifies the type of input field to display. Possible values are 'text', 'password', 'email', 'number', 'tel', 'search', 'url', and more. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types | `"date" \| "datetime-local" \| "email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "time" \| "url"` | `'text'` | +| `validationStatus` | `validation-status` | The validation status of the input. | `"error" \| "none" \| "success" \| "warning"` | `'none'` | +| `value` | `value` | The input value, it can be used to reset the input to a previous value | `number \| string \| string[]` | `undefined` | + + +## Events + +| Event | Description | Type | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| `bqBlur` | Callback handler emitted when the input loses focus | `CustomEvent` | +| `bqChange` | Callback handler emitted when the input value has changed and the input loses focus. This handler is called whenever the user finishes typing or pasting text into the input field and then clicks outside of the input field. | `CustomEvent<{ value: string \| number \| string[]; el: HTMLBqInputElement; }>` | +| `bqClear` | Callback handler emitted when the input value has been cleared | `CustomEvent` | +| `bqFocus` | Callback handler emitted when the input has received focus | `CustomEvent` | +| `bqInput` | Callback handler emitted when the input value changes. This handler is called whenever the user types or pastes text into the input field. | `CustomEvent<{ value: string \| number \| string[]; el: HTMLBqInputElement; }>` | + + +## Shadow Parts + +| Part | Description | +| --------------- | --------------------------------------------------------------- | +| `"base"` | The component's base wrapper. | +| `"button"` | The native HTML button used under the hood in the clear button. | +| `"clear-btn"` | The clear button. | +| `"control"` | The input control wrapper. | +| `"helper-text"` | The helper text slot container. | +| `"input"` | The native HTML input element used under the hood. | +| `"label"` | The label slot container. | +| `"prefix"` | The prefix slot container. | +| `"suffix"` | The suffix slot container. | + + +## Dependencies + +### Depends on + +- [bq-button](../button) +- [bq-icon](../icon) + +### Graph +```mermaid +graph TD; + bq-input --> bq-button + bq-input --> bq-icon + bq-button --> bq-icon + style bq-input fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/bee-q/src/components/input/scss/bq-input.scss b/packages/bee-q/src/components/input/scss/bq-input.scss new file mode 100644 index 000000000..ea761d715 --- /dev/null +++ b/packages/bee-q/src/components/input/scss/bq-input.scss @@ -0,0 +1,153 @@ +/* -------------------------------------------------------------------------- */ +/* Input styles */ +/* -------------------------------------------------------------------------- */ + +@import './bq-input.variables'; + +:host { + @apply block; +} + +/* -------------------------------------------------------------------------- */ +/* Label and Helper text */ +/* -------------------------------------------------------------------------- */ + +.bq-input--label { + @apply mb-[--bq-input--label-margin-bottom] flex flex-grow items-center gap-[--var(--bq-input--gap-label)]; + @apply text-[length:--bq-input--label-text-size] text-[color:--bq-input--label-text-color]; +} + +.bq-input--helper-text { + @apply mt-[--bq-input--helper-margin-top] text-[length:--bq-input--helper-text-size] text-[color:--bq-input--helper-text-color]; +} + +.bq-input--helper-text.validation-error { + @apply text-text-danger; +} + +.bq-input--helper-text.validation-success { + @apply text-text-success; +} + +.bq-input--helper-text.validation-warning { + @apply text-text-warning; +} + +/* -------------------------------------------------------------------------- */ +/* Input group control */ +/* -------------------------------------------------------------------------- */ + +.bq-input--control { + @apply flex w-full items-center transition-[border-color,box-shadow]; + // Border + @apply rounded-[--bq-input--border-radius] border-[length:--bq-input--border-width] border-[color:--bq-input--border-color]; + // Padding + @apply py-[--bq-input--paddingY] pe-[--bq-input--pading-end] ps-[--bq-input--pading-start]; + // Text + @apply text-[length:--bq-input--text-size] text-[color:--bq-input--text-color] placeholder:text-[color:--bq-input--text-placeholder-color]; + // Hover + @apply [&:not(:focus-within):not(.disabled)]:hover:border-[color:--bq-input--border-color-hover]; + + border-style: var(--bq-input--border-style); + + // Focus + &:not(.disabled) { + @apply focus-within:border-[color:--bq-input--border-color-focus] focus-within:outline-none focus-within:ring-1 focus-within:ring-[color:--bq-input--border-color-focus]; + } +} + +.bq-input--control.disabled { + @apply cursor-not-allowed border-[color:--bq-stroke--tiertary-disabled] bg-ui-secondary-disabled; +} + +/* ------------------------------- Validation ------------------------------- */ + +.bq-input--control.validation-error { + @apply border-stroke-danger focus-within:border-stroke-danger-active focus-within:ring-stroke-danger-active [&:not(:focus-within)]:hover:border-stroke-danger-hover; +} + +.bq-input--control.validation-success { + @apply border-stroke-success focus-within:border-stroke-success-active focus-within:ring-stroke-success-active [&:not(:focus-within)]:hover:border-stroke-success-hover; +} + +.bq-input--control.validation-warning { + @apply border-stroke-warning focus-within:border-stroke-warning-active focus-within:ring-stroke-warning-active [&:not(:focus-within)]:hover:border-stroke-warning-hover; +} + +/* -------------------------------------------------------------------------- */ +/* Native HTML Input */ +/* -------------------------------------------------------------------------- */ + +.bq-input--control__input { + @apply flex-auto cursor-[inherit] appearance-none bg-[inherit] font-[inherit] text-[length:inherit]; + @apply m-0 min-h-[--bq-input--icon-size] min-w-[0] border-none p-0 focus:outline-none focus-visible:outline-none; + + box-shadow: none; + font-weight: inherit; +} + +/* -------------------------------------------------------------------------- */ +/* Clear button */ +/* -------------------------------------------------------------------------- */ + +.bq-input--control__clear::part(button) { + @apply h-auto rounded-xs border-none p-0; +} + +/* -------------------------------------------------------------------------- */ +/* Prefix and suffix */ +/* -------------------------------------------------------------------------- */ + +.bq-input--control__prefix, +.bq-input--control__suffix { + @apply pointer-events-none flex items-center text-[color:var(--bq-input--text-color)]; +} + +.bq-input--control__prefix { + @apply me-[--bq-input--gap]; +} + +.bq-input--control__suffix { + @apply ms-[--bq-input--gap]; +} + +/* -------------------------------------------------------------------------- */ +/* Slotted and internal icons */ +/* -------------------------------------------------------------------------- */ + +bq-icon, +::slotted(bq-icon) { + --bq-icon--size: var(--bq-input--icon-size) !important; +} + +/* -------------------------------------------------------------------------- */ +/* Hide webkit clear button */ +/* -------------------------------------------------------------------------- */ + +/* Remove clear controls from search input */ + +.bq-input--control__input::-moz-search-cancel { + @apply hidden appearance-none; +} + +.bq-input--control__input::-webkit-search-decoration, +.bq-input--control__input::-webkit-search-cancel-button, +.bq-input--control__input::-webkit-search-results-button, +.bq-input--control__input::-webkit-search-results-decoration { + @apply hidden appearance-none; +} + +/* Remove native control extra padding from input date and datetime */ + +.bq-input--control__input::-webkit-datetime-edit-fields-wrapper, +.bq-input--control__input::-webkit-datetime-edit, +.bq-input--control__input::-webkit-datetime-edit-year-field, +.bq-input--control__input::-webkit-datetime-edit-month-field, +.bq-input--control__input::-webkit-datetime-edit-day-field, +.bq-input--control__input::-webkit-datetime-edit-hour-field, +.bq-input--control__input::-webkit-datetime-edit-minute-field, +.bq-input--control__input::-webkit-datetime-edit-second-field, +.bq-input--control__input::-webkit-datetime-edit-millisecond-field, +.bq-input--control__input::-webkit-datetime-edit-meridiem-field { + @apply p-0; +} diff --git a/packages/bee-q/src/components/input/scss/bq-input.variables.scss b/packages/bee-q/src/components/input/scss/bq-input.variables.scss new file mode 100644 index 000000000..dda4347b6 --- /dev/null +++ b/packages/bee-q/src/components/input/scss/bq-input.variables.scss @@ -0,0 +1,59 @@ +/* -------------------------------------------------------------------------- */ +/* Input custom properties */ +/* -------------------------------------------------------------------------- */ + +:host { + /** + * @prop --bq-input--background-color - Input background color + * @prop --bq-input--border-color - Input border color + * @prop --bq-input--border-color-hover - Input border color on hover + * @prop --bq-input--border-color-focus - Input border color on focus + * @prop --bq-input--border-color-disabled - Input border color when disabled + * @prop --bq-input--border-radius - Input border radius + * @prop --bq-input--border-width - Input border width + * @prop --bq-input--border-style - Input border style + * @prop --bq-input--gap - Gap between input content and prefix/suffix + * @prop --bq-input--helper-margin-top - Helper text margin top + * @prop --bq-input--helper-text-color - Helper text color + * @prop --bq-input--helper-text-size - Helper text size + * @prop --bq-input--icon-size - Icon size to use in prefix/suffix and clear button + * @prop --bq-input--label-margin-bottom - Input label margin bottom + * @prop --bq-input--label-text-color - Input label text color + * @prop --bq-input--label-text-size - Input label text size + * @prop --bq-input--pading-start - Input padding start + * @prop --bq-input--pading-end - Input padding end + * @prop --bq-input--paddingY - Input padding top and bottom + * @prop --bq-input--text-color - Input text color + * @prop --bq-input--text-size - Input text size + * @prop --bq-input--text-placeholder-color - Input placeholder text color + */ + --bq-input--background-color: var(--bq-neutral-white); + + --bq-input--border-color: theme('colors.stroke.tiertary'); + --bq-input--border-color-hover: theme('colors.stroke.brand-hover'); + --bq-input--border-color-focus: theme('colors.stroke.brand'); + --bq-input--border-color-disabled: theme('colors.stroke.tiertary-disabled'); + --bq-input--border-radius: theme('borderRadius.s'); + --bq-input--border-width: 1px; + --bq-input--border-style: solid; + + --bq-input--gap: theme('spacing.xs'); + + --bq-input--helper-margin-top: theme('spacing.xs'); + --bq-input--helper-text-size: theme('fontSize.s'); + --bq-input--helper-text-color: theme('colors.text.primary'); + + --bq-input--icon-size: 24px; + + --bq-input--label-margin-bottom: theme('spacing.xs'); + --bq-input--label-text-size: theme('fontSize.s'); + --bq-input--label-text-color: theme('colors.text.primary'); + + --bq-input--pading-start: theme('spacing.m'); + --bq-input--pading-end: theme('spacing.m'); + --bq-input--paddingY: theme('spacing.s'); + + --bq-input--text-color: theme('colors.text.primary'); + --bq-input--text-size: theme('fontSize.m'); + --bq-input--text-placeholder-color: theme('colors.text.secondary-disabled'); +} diff --git a/packages/bee-q/src/components/tooltip/bq-tooltip.tsx b/packages/bee-q/src/components/tooltip/bq-tooltip.tsx index 796e3d822..3e30a9787 100644 --- a/packages/bee-q/src/components/tooltip/bq-tooltip.tsx +++ b/packages/bee-q/src/components/tooltip/bq-tooltip.tsx @@ -173,7 +173,7 @@ export class BqTooltip {
{/* TRIGGER */}