From ddb077c549fff7e3183c04d4622920c800573aae Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Tue, 13 May 2025 05:59:49 -0700 Subject: [PATCH 1/9] feat(@clayui/css): LPD-53483 Adds inline-text-input --- .../src/scss/cadmin/variables/_forms.scss | 80 ++++++++++++++++++- packages/clay-css/src/scss/mixins/_forms.scss | 47 ++++++++++- .../clay-css/src/scss/variables/_forms.scss | 80 ++++++++++++++++++- 3 files changed, 203 insertions(+), 4 deletions(-) diff --git a/packages/clay-css/src/scss/cadmin/variables/_forms.scss b/packages/clay-css/src/scss/cadmin/variables/_forms.scss index d7cf64feb5..c215ad67c2 100644 --- a/packages/clay-css/src/scss/cadmin/variables/_forms.scss +++ b/packages/clay-css/src/scss/cadmin/variables/_forms.scss @@ -129,6 +129,59 @@ $cadmin-input-palette: map-deep-merge( white-space: nowrap, width: min-content, ), + '.form-control-fit-content': ( + max-width: 100%, + '.form-control': ( + display: inline-flex, + width: auto, + ), + '.form-control-inset': ( + margin: 0, + min-height: 0, + min-width: 50px, + width: auto, + '&:empty:before': ( + color: $secondary, + content: unquote("'\\FEFF' attr(placeholder)"), + cursor: text, + pointer-events: none, + ), + '&:focus:empty:before': ( + content: none, + ), + ), + '.form-control-hidden': ( + width: 0, + ), + ), + '.inline-text-input': ( + '.form-control': ( + background-color: transparent, + border-color: transparent, + font-size: 1.75rem, + font-weight: $font-weight-semi-bold, + line-height: 1, + padding: 0.3125rem 0 0.3125rem 1rem, + '&:hover': ( + border-color: $secondary-l3, + ), + ), + '.form-control-inset': ( + padding-right: 1rem, + '&:focus ~ .form-control-indicator': ( + display: none, + ), + '&[contenteditable="false"] ~ .form-control-indicator': ( + display: none, + ), + ), + '.form-control-item': ( + padding-right: 1rem, + ), + '.lexicon-icon': ( + font-size: 1rem, + ), + ), ), $cadmin-input-palette ); @@ -421,9 +474,10 @@ $cadmin-form-control-inset: map-deep-merge( margin-bottom: $cadmin-form-control-inset-margin-y, margin-left: 8px, margin-top: $cadmin-form-control-inset-margin-y, + max-width: 100%, min-height: $cadmin-form-control-inset-min-height, + min-width: 50px, padding: 0, - width: 50px, focus: ( outline: 0, ), @@ -840,6 +894,14 @@ $cadmin-has-error: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-danger-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $cadmin-has-error ); @@ -1010,6 +1072,14 @@ $cadmin-has-warning: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-warning-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $cadmin-has-warning ); @@ -1180,6 +1250,14 @@ $cadmin-has-success: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-success-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $cadmin-has-success ); diff --git a/packages/clay-css/src/scss/mixins/_forms.scss b/packages/clay-css/src/scss/mixins/_forms.scss index 302b382496..3598c00605 100644 --- a/packages/clay-css/src/scss/mixins/_forms.scss +++ b/packages/clay-css/src/scss/mixins/_forms.scss @@ -607,8 +607,11 @@ $_focus-within: map-get($map, focus-within); @if ($_focus-within) { - &:focus-within:has(input:focus) { - @include clay-form-control-variant($_focus-within); + &:focus-within { + &:has(input:focus), + &:has([contenteditable]:focus) { + @include clay-form-control-variant($_focus-within); + } } } @@ -644,6 +647,14 @@ } } + $_readonly: map-get($map, readonly); + + @if ($_readonly) { + &[readonly] { + @include clay-form-control-variant($_readonly); + } + } + // Disabled // HTML5 says that controls under a fieldset > legend:first-child won't be // disabled if the fieldset is disabled. Due to implementation difficulty, we @@ -700,6 +711,22 @@ } } + @each $key, $value in $map { + @if not clay-is-map-unset($value) { + $selector: if( + starts-with($key, '.') or starts-with($key, '#'), + $key, + false + ); + + @if ($selector) { + #{$selector} { + @include clay-map-to-css($value); + } + } + } + } + @include clay-generate-media-breakpoints($map); } } @@ -1605,6 +1632,22 @@ } } } + + @each $key, $value in $map { + @if not clay-is-map-unset($value) { + $selector: if( + starts-with($key, '.') or starts-with($key, '#'), + $key, + false + ); + + @if ($selector) { + #{$selector} { + @include clay-map-to-css($value); + } + } + } + } } } } diff --git a/packages/clay-css/src/scss/variables/_forms.scss b/packages/clay-css/src/scss/variables/_forms.scss index f165126241..685cbf2575 100644 --- a/packages/clay-css/src/scss/variables/_forms.scss +++ b/packages/clay-css/src/scss/variables/_forms.scss @@ -127,6 +127,59 @@ $input-palette: map-deep-merge( white-space: nowrap, width: min-content, ), + '.form-control-fit-content': ( + max-width: 100%, + '.form-control': ( + display: inline-flex, + width: auto, + ), + '.form-control-inset': ( + margin: 0, + min-height: 0, + min-width: 50px, + width: auto, + '&:empty:before': ( + color: $secondary, + content: unquote("'\\FEFF' attr(placeholder)"), + cursor: text, + pointer-events: none, + ), + '&:focus:empty:before': ( + content: none, + ), + ), + '.form-control-hidden': ( + width: 0, + ), + ), + '.inline-text-input': ( + '.form-control': ( + background-color: transparent, + border-color: transparent, + font-size: 1.75rem, + font-weight: $font-weight-semi-bold, + line-height: 1, + padding: 0.3125rem 0 0.3125rem 1rem, + '&:hover': ( + border-color: $secondary-l3, + ), + ), + '.form-control-inset': ( + padding-right: 1rem, + '&:focus ~ .form-control-indicator': ( + display: none, + ), + '&[contenteditable="false"] ~ .form-control-indicator': ( + display: none, + ), + ), + '.form-control-item': ( + padding-right: 1rem, + ), + '.lexicon-icon': ( + font-size: 1rem, + ), + ), ), $input-palette ); @@ -468,9 +521,10 @@ $form-control-inset: map-deep-merge( margin-bottom: $form-control-inset-margin-y, margin-left: 0.25rem, margin-top: $form-control-inset-margin-y, + max-width: 100%, min-height: $form-control-inset-min-height, + min-width: 50px, padding: 0, - width: 50px, focus: ( outline: 0, ), @@ -922,6 +976,14 @@ $has-error: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-danger-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $has-error ); @@ -1075,6 +1137,14 @@ $has-warning: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-warning-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $has-warning ); @@ -1228,6 +1298,14 @@ $has-success: map-deep-merge( ), ), ), + '.inline-text-input': ( + '.form-control': ( + border-color: $input-success-border-color, + ), + '.form-control-indicator': ( + display: none, + ), + ), ), $has-success ); From e6e165bffe2d82581869828b209124ee04dbeccc Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Tue, 13 May 2025 07:50:53 -0700 Subject: [PATCH 2/9] feat(@clayui/form): LPD-53483 Add ClayInput.InlineText --- packages/clay-form/src/Input.tsx | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/packages/clay-form/src/Input.tsx b/packages/clay-form/src/Input.tsx index 0e0c672130..7df9c52c13 100644 --- a/packages/clay-form/src/Input.tsx +++ b/packages/clay-form/src/Input.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ +import ClayIcon from '@clayui/icon'; import classNames from 'classnames'; import React from 'react'; @@ -155,6 +156,97 @@ const GroupInsetItem = React.forwardRef< GroupInsetItem.displayName = 'ClayInputGroupInsetItem'; +interface IInlineTextProps + extends React.HTMLAttributes< + HTMLDivElement | HTMLSpanElement | HTMLLabelElement + > { + /** + * The id of the component + */ + id?: string; + + /** + * Text that hints at the expected data type to be entered. + */ + placeholder?: string; + + /** + * A flag that sets the component as noneditable. + */ + readOnly?: boolean; + + /** + * Path to the spritemap that Icon should use when referencing symbols. + */ + spritemap?: string; +} + +const InlineText = React.forwardRef< + HTMLDivElement | HTMLSpanElement | HTMLLabelElement, + IInlineTextProps +>( + ({ + children, + className, + id, + placeholder, + readOnly, + spritemap, + ...otherProps + }: IInlineTextProps) => { + const inputRef = React.useRef(null); + + const [inputValue, setInputValue] = React.useState(''); + + return ( +
+
+ { + inputRef.current?.focus(); + }} + readOnly + tabIndex={-1} + type="text" + value={inputValue} + /> + { + if (inputValue.trim() === '') { + const input = event.target as HTMLElement; + + input.innerHTML = ''; + } + }} + onInput={(event) => { + const input = event.target as HTMLElement; + + setInputValue(input.innerText); + }} + placeholder={placeholder} + ref={inputRef} + role="textbox" + tabIndex={0} + {...otherProps} + /> + + + + {children} +
+
+ ); + } +); + +InlineText.displayName = 'ClayInlineText'; + interface IProps extends React.InputHTMLAttributes { /** * Input component to render. Can either be a string like 'input' or 'textarea' or a component. @@ -183,6 +275,7 @@ interface IForwardRef GroupInsetItem: typeof GroupInsetItem; GroupItem: typeof GroupItem; GroupText: typeof GroupText; + InlineText: typeof InlineText; } function forwardRef(component: React.RefForwardingComponent) { @@ -221,5 +314,6 @@ Input.Group = Group; Input.GroupInsetItem = GroupInsetItem; Input.GroupItem = GroupItem; Input.GroupText = GroupText; +Input.InlineText = InlineText; export default Input; From 708e5449a79fe6dbc1a5b6f49c47bc45f97658c7 Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Tue, 13 May 2025 08:50:42 -0700 Subject: [PATCH 3/9] chore(@clayui/form): LPD-53483 Add storybook example of ClayInput.InlineText --- packages/clay-form/stories/Input.stories.tsx | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/clay-form/stories/Input.stories.tsx b/packages/clay-form/stories/Input.stories.tsx index 07171b28e0..56d91ed9b0 100644 --- a/packages/clay-form/stories/Input.stories.tsx +++ b/packages/clay-form/stories/Input.stories.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ +import classNames from 'classnames'; import React from 'react'; import {ClayInput} from '../src'; @@ -86,3 +87,29 @@ Textarea.args = { readOnly: false, sizing: undefined, }; + +export const InlineText = (args: any) => ( +
+ + + + +
+); + +InlineText.args = { + hasError: false, + hasSuccess: false, + hasWarning: false, + readOnly: false, +}; From 648cadbf3b028c08278e9720fcd9299e61887f27 Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Fri, 16 May 2025 14:57:08 -0700 Subject: [PATCH 4/9] fix(@clayui/css): LPD-53483 Cadmin Form prefix variables with $cadmin- --- .../src/scss/cadmin/variables/_forms.scss | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/clay-css/src/scss/cadmin/variables/_forms.scss b/packages/clay-css/src/scss/cadmin/variables/_forms.scss index c215ad67c2..de3f19ce81 100644 --- a/packages/clay-css/src/scss/cadmin/variables/_forms.scss +++ b/packages/clay-css/src/scss/cadmin/variables/_forms.scss @@ -141,7 +141,7 @@ $cadmin-input-palette: map-deep-merge( min-width: 50px, width: auto, '&:empty:before': ( - color: $secondary, + color: $cadmin-secondary, content: unquote("'\\FEFF' attr(placeholder)"), cursor: text, pointer-events: none, @@ -158,16 +158,16 @@ $cadmin-input-palette: map-deep-merge( '.form-control': ( background-color: transparent, border-color: transparent, - font-size: 1.75rem, - font-weight: $font-weight-semi-bold, + font-size: 28px, + font-weight: $cadmin-font-weight-semi-bold, line-height: 1, - padding: 0.3125rem 0 0.3125rem 1rem, + padding: 5px 0 5px 16px, '&:hover': ( - border-color: $secondary-l3, + border-color: $cadmin-secondary-l3, ), ), '.form-control-inset': ( - padding-right: 1rem, + padding-right: 16px, '&:focus ~ .form-control-indicator': ( display: none, ), @@ -176,10 +176,10 @@ $cadmin-input-palette: map-deep-merge( ), ), '.form-control-item': ( - padding-right: 1rem, + padding-right: 16px, ), '.lexicon-icon': ( - font-size: 1rem, + font-size: 16px, ), ), ), @@ -896,7 +896,7 @@ $cadmin-has-error: map-deep-merge( ), '.inline-text-input': ( '.form-control': ( - border-color: $input-danger-border-color, + border-color: $cadmin-input-danger-border-color, ), '.form-control-indicator': ( display: none, @@ -1074,7 +1074,7 @@ $cadmin-has-warning: map-deep-merge( ), '.inline-text-input': ( '.form-control': ( - border-color: $input-warning-border-color, + border-color: $cadmin-input-warning-border-color, ), '.form-control-indicator': ( display: none, @@ -1252,7 +1252,7 @@ $cadmin-has-success: map-deep-merge( ), '.inline-text-input': ( '.form-control': ( - border-color: $input-success-border-color, + border-color: $cadmin-input-success-border-color, ), '.form-control-indicator': ( display: none, From 3d893a5000122cb46e6b023c510a3266654b1a73 Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Fri, 16 May 2025 14:59:01 -0700 Subject: [PATCH 5/9] fix(@clayui/form): LPD-53483 SF --- packages/clay-form/src/Input.tsx | 69 ++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/packages/clay-form/src/Input.tsx b/packages/clay-form/src/Input.tsx index 7df9c52c13..092ff9b545 100644 --- a/packages/clay-form/src/Input.tsx +++ b/packages/clay-form/src/Input.tsx @@ -4,6 +4,7 @@ */ import ClayIcon from '@clayui/icon'; +import {InternalDispatch, useControlledState} from '@clayui/shared'; import classNames from 'classnames'; import React from 'react'; @@ -157,14 +158,27 @@ const GroupInsetItem = React.forwardRef< GroupInsetItem.displayName = 'ClayInputGroupInsetItem'; interface IInlineTextProps - extends React.HTMLAttributes< - HTMLDivElement | HTMLSpanElement | HTMLLabelElement - > { + extends React.HTMLAttributes { + /** + * The initial value of the input (uncontrolled). + */ + defaultInputValue?: string; + /** * The id of the component */ id?: string; + /** + * The current value of the input (controlled). + */ + inputValue?: string; + + /** + * Callback called when input value changes (controlled). + */ + onInputValueChange?: InternalDispatch; + /** * Text that hints at the expected data type to be entered. */ @@ -181,25 +195,38 @@ interface IInlineTextProps spritemap?: string; } -const InlineText = React.forwardRef< - HTMLDivElement | HTMLSpanElement | HTMLLabelElement, - IInlineTextProps ->( - ({ - children, - className, - id, - placeholder, - readOnly, - spritemap, - ...otherProps - }: IInlineTextProps) => { - const inputRef = React.useRef(null); - - const [inputValue, setInputValue] = React.useState(''); +const InlineText = React.forwardRef( + ( + { + children, + className, + defaultInputValue, + id, + inputValue: externalInputValue, + onInputValueChange, + placeholder, + readOnly, + spritemap, + ...otherProps + }: IInlineTextProps, + ref: React.Ref + ) => { + const inputRef = React.useRef(null); + + const [inputValue = '', setInputValue] = useControlledState({ + defaultName: 'defaultInputValue', + defaultValue: defaultInputValue, + handleName: 'onInputValueChange', + name: 'inputValue', + onChange: onInputValueChange, + value: externalInputValue, + }); return ( -
+
@@ -232,7 +258,6 @@ const InlineText = React.forwardRef< placeholder={placeholder} ref={inputRef} role="textbox" - tabIndex={0} {...otherProps} /> From 15d40b1464bd29c699faa583377a13c42b4ce064 Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Mon, 19 May 2025 07:18:23 -0700 Subject: [PATCH 6/9] chore(@clayui/form): LPD-53483 add Input.InlineText test --- packages/clay-form/src/__tests__/index.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/clay-form/src/__tests__/index.tsx b/packages/clay-form/src/__tests__/index.tsx index 77687124d9..f1fdcf1ec0 100644 --- a/packages/clay-form/src/__tests__/index.tsx +++ b/packages/clay-form/src/__tests__/index.tsx @@ -57,4 +57,16 @@ describe('ClayForm', () => { expect(testRenderer.toJSON()).toMatchSnapshot(); }); + + it('Input.GroupInsetItem renders', () => { + const testRenderer = TestRenderer.create(); + + expect(testRenderer.toJSON()).toMatchSnapshot(); + }); + + it('Input.InlineText renders', () => { + const testRenderer = TestRenderer.create(); + + expect(testRenderer.toJSON()).toMatchSnapshot(); + }); }); From fb35ff7cca764a07aa89c572c6cf7bf7d4b4884c Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Mon, 19 May 2025 10:27:33 -0700 Subject: [PATCH 7/9] chore(@clayui/form): LPD-53483 Update snapshot --- .../__tests__/__snapshots__/index.tsx.snap | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/clay-form/src/__tests__/__snapshots__/index.tsx.snap b/packages/clay-form/src/__tests__/__snapshots__/index.tsx.snap index 200b0449be..3dde9d751d 100644 --- a/packages/clay-form/src/__tests__/__snapshots__/index.tsx.snap +++ b/packages/clay-form/src/__tests__/__snapshots__/index.tsx.snap @@ -39,12 +39,56 @@ exports[`ClayForm Input.Group renders 1`] = ` /> `; +exports[`ClayForm Input.GroupInsetItem renders 1`] = ` +
+`; + exports[`ClayForm Input.GroupItem renders 1`] = `
`; +exports[`ClayForm Input.InlineText renders 1`] = ` +
+
+ + + + + + + +
+
+`; + exports[`ClayForm Text renders 1`] = `
Date: Mon, 19 May 2025 10:27:51 -0700 Subject: [PATCH 8/9] docs(clayui.com): LPD-53483 Add examples of InlineText --- packages/clay-form/docs/form.mdx | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/clay-form/docs/form.mdx b/packages/clay-form/docs/form.mdx index 82a0decd17..7af858016d 100644 --- a/packages/clay-form/docs/form.mdx +++ b/packages/clay-form/docs/form.mdx @@ -41,6 +41,53 @@ export default function App() { } ``` +## Inline Text Input + +A text input that grows along with its content. + +```jsx preview +import {Provider} from '@clayui/core'; +import Form, {ClayInput} from '@clayui/form'; +import React, {useState} from 'react'; + +import '@clayui/css/lib/css/atlas.css'; + +export default function App() { + return ( + +
+
+ + + + + + + + + + + +
+
+
+ ); +} +``` + ## Validation `.has-error`, `.has-success` and `.has-warning` classes are used in `ClayForm.Group` to identifier validation state of the form group items. From 7c807f97f0dc15902830c59f048f1e96a414bebc Mon Sep 17 00:00:00 2001 From: Patrick Yeo Date: Tue, 20 May 2025 16:34:28 -0700 Subject: [PATCH 9/9] Uncontrolled only works --- packages/clay-form/src/Input.tsx | 46 ++++++++++---------- packages/clay-form/stories/Input.stories.tsx | 9 ++-- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/packages/clay-form/src/Input.tsx b/packages/clay-form/src/Input.tsx index 092ff9b545..3b98c9112e 100644 --- a/packages/clay-form/src/Input.tsx +++ b/packages/clay-form/src/Input.tsx @@ -200,28 +200,28 @@ const InlineText = React.forwardRef( { children, className, - defaultInputValue, - id, - inputValue: externalInputValue, - onInputValueChange, + defaultValue, + onValueChange, placeholder, - readOnly, spritemap, + value: externalValue, ...otherProps }: IInlineTextProps, ref: React.Ref ) => { const inputRef = React.useRef(null); - const [inputValue = '', setInputValue] = useControlledState({ - defaultName: 'defaultInputValue', - defaultValue: defaultInputValue, - handleName: 'onInputValueChange', - name: 'inputValue', - onChange: onInputValueChange, - value: externalInputValue, + const [value = '', setValue] = useControlledState({ + defaultName: 'defaultValue', + defaultValue: defaultValue, + handleName: 'onValueChange', + name: 'value', + onChange: onValueChange, + value: externalValue, }); + const initialValue = value; + return (
( >
{ + onFocus={(event) => { inputRef.current?.focus(); }} - readOnly type="text" - value={inputValue} + value={value} + {...otherProps} /> { - if (inputValue.trim() === '') { + if (value.trim() === '') { const input = event.target as HTMLElement; input.innerHTML = ''; @@ -253,13 +253,15 @@ const InlineText = React.forwardRef( onInput={(event) => { const input = event.target as HTMLElement; - setInputValue(input.innerText); + setValue(input.innerText); }} placeholder={placeholder} ref={inputRef} role="textbox" - {...otherProps} - /> + suppressContentEditableWarning={true} + > + {defaultValue} + diff --git a/packages/clay-form/stories/Input.stories.tsx b/packages/clay-form/stories/Input.stories.tsx index 56d91ed9b0..5e869ed158 100644 --- a/packages/clay-form/stories/Input.stories.tsx +++ b/packages/clay-form/stories/Input.stories.tsx @@ -99,9 +99,12 @@ export const InlineText = (args: any) => ( >
@@ -111,5 +114,5 @@ InlineText.args = { hasError: false, hasSuccess: false, hasWarning: false, - readOnly: false, + readonly: false, };