-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add SfTextarea component (#30)
* WIP: add SfTextarea * fix: change class to wrapper class * fix: remove unused classes * feat: add showcase TextAreaCharacters * feat: add showcase textarea disabled * feat: add showcase textarea Invalid * feat: add showcase textarea readOnly * feat: add showcase textarea autoresize * fix: onInput textarea * chore: delete unused file * chore: delete unused file browser * chore: delete unused file types * chore: delete unused file index
- Loading branch information
1 parent
9cf058f
commit 532fc27
Showing
13 changed files
with
449 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { $, component$, useContext, useTask$ } from '@builder.io/qwik'; | ||
import { SfTextarea, SfTextareaSize } from 'qwik-storefront-ui'; | ||
import { ComponentExample } from '../../../components/utils/ComponentExample'; | ||
import { ControlsType } from '../../../components/utils/types'; | ||
import { EXAMPLES_STATE } from '../layout'; | ||
|
||
export default component$(() => { | ||
const examplesState = useContext(EXAMPLES_STATE); | ||
|
||
useTask$(() => { | ||
examplesState.data = { | ||
controls: [ | ||
{ | ||
type: 'select', | ||
modelName: 'size', | ||
propDefaultValue: 'SfInputSize.base', | ||
propType: 'SfInputSize', | ||
options: Object.keys(SfTextareaSize), | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'string', | ||
modelName: 'label', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'string', | ||
modelName: 'placeholder', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'string', | ||
modelName: 'helpText', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'string', | ||
modelName: 'requiredText', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'string', | ||
modelName: 'errorText', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'text', | ||
propType: 'number', | ||
modelName: 'characterLimit', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'boolean', | ||
propType: 'boolean', | ||
modelName: 'disabled', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'boolean', | ||
propType: 'boolean', | ||
modelName: 'required', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'boolean', | ||
propType: 'boolean', | ||
modelName: 'invalid', | ||
isRequired: false, | ||
}, | ||
{ | ||
type: 'boolean', | ||
propType: 'boolean', | ||
modelName: 'readonly', | ||
isRequired: false, | ||
}, | ||
] satisfies ControlsType, | ||
state: { | ||
size: SfTextareaSize.base, | ||
disabled: false, | ||
required: false, | ||
invalid: false, | ||
readonly: undefined, | ||
placeholder: 'Write something about yourself', | ||
helpText: 'Do not include personal or financial information.', | ||
requiredText: 'Required text', | ||
errorText: 'Error message', | ||
label: 'Description', | ||
characterLimit: 12, | ||
value: '', | ||
}, | ||
}; | ||
}); | ||
|
||
const onInput = $((e: Event) => { | ||
const target = e.target as HTMLInputElement; | ||
|
||
examplesState.data.state = { | ||
...examplesState.data.state, | ||
value: target.value, | ||
}; | ||
}); | ||
|
||
const isAboveLimit = examplesState.data.state.characterLimit | ||
? examplesState.data.state.value.length > | ||
examplesState.data.state.characterLimit | ||
: false; | ||
|
||
const charsCount = examplesState.data.state.characterLimit | ||
? examplesState.data.state.characterLimit - | ||
examplesState.data.state.value.length | ||
: null; | ||
|
||
const getCharacterLimitClass = () => | ||
isAboveLimit ? 'text-negative-700 font-medium' : 'text-neutral-500'; | ||
|
||
return ( | ||
<ComponentExample> | ||
<label> | ||
<span | ||
class={[ | ||
'typography-text-sm font-medium', | ||
{ | ||
'cursor-not-allowed text-disabled-500': | ||
examplesState.data.state.disabled, | ||
}, | ||
]} | ||
> | ||
{examplesState.data.state.label} | ||
</span> | ||
<SfTextarea | ||
name={examplesState.data.state.label} | ||
size={examplesState.data.state.size} | ||
value={examplesState.data.state.value} | ||
invalid={examplesState.data.state.invalid} | ||
placeholder={examplesState.data.state.placeholder} | ||
disabled={examplesState.data.state.disabled} | ||
readOnly={examplesState.data.state.readonly} | ||
onInput$={onInput} | ||
wrapperClass={[ | ||
`w-full block h-2/5 ${ | ||
examplesState.data.state.disabled | ||
? '!bg-disabled-100 !ring-disabled-300 !ring-1 !text-disabled-500' | ||
: examplesState.data.state.readonly && | ||
'!bg-disabled-100 !ring-disabled-300 !ring-1 !text-neutral-500' | ||
}`, | ||
]} | ||
/> | ||
</label> | ||
<div class="flex justify-between"> | ||
<div> | ||
{examplesState.data.state.invalid && | ||
!examplesState.data.state.disabled && ( | ||
<p class="typography-error-sm text-negative-700 font-medium mt-0.5"> | ||
{examplesState.data.state.errorText} | ||
</p> | ||
)} | ||
{examplesState.data.state.helpText && ( | ||
<p | ||
class={[ | ||
'typography-hint-xs mt-0.5', | ||
examplesState.data.state.disabled | ||
? 'text-disabled-500' | ||
: 'text-neutral-500', | ||
]} | ||
> | ||
{examplesState.data.state.helpText} | ||
</p> | ||
)} | ||
{examplesState.data.state.requiredText && | ||
examplesState.data.state.required ? ( | ||
<p class="mt-1 typography-text-sm font-normal text-neutral-500 before:content-['*']"> | ||
{examplesState.data.state.requiredText} | ||
</p> | ||
) : null} | ||
</div> | ||
{examplesState.data.state.characterLimit && | ||
!examplesState.data.state.readonly ? ( | ||
<p | ||
class={[ | ||
'typography-error-xs mt-0.5', | ||
examplesState.data.state.disabled | ||
? 'text-disabled-500' | ||
: getCharacterLimitClass(), | ||
]} | ||
> | ||
{charsCount} | ||
</p> | ||
) : null} | ||
</div> | ||
</ComponentExample> | ||
); | ||
}); |
29 changes: 29 additions & 0 deletions
29
apps/website/src/routes/showcases/Textarea/TextareaAutoresize/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { component$, useSignal, useTask$ } from '@builder.io/qwik'; | ||
import { attach } from '@frsource/autoresize-textarea'; | ||
import { SfTextarea } from 'qwik-storefront-ui'; | ||
|
||
export default component$(() => { | ||
const textareaRef = useSignal<HTMLTextAreaElement>(); | ||
|
||
useTask$(() => { | ||
if (textareaRef.value) { | ||
attach(textareaRef.value); | ||
} | ||
}); | ||
return ( | ||
<> | ||
<label> | ||
<span class="typography-text-sm font-medium">Description</span> | ||
<SfTextarea | ||
ref={textareaRef} | ||
wrapperClass={['w-full h-max-[500px] block']} | ||
size="sm" | ||
aria-label="Label size sm" | ||
/> | ||
</label> | ||
<p class="typography-hint-xs text-neutral-500 mt-0.5"> | ||
Do not include personal or financial information. | ||
</p> | ||
</> | ||
); | ||
}); |
76 changes: 76 additions & 0 deletions
76
apps/website/src/routes/showcases/Textarea/TextareaCharacters/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { $, component$, useComputed$, useSignal } from '@builder.io/qwik'; | ||
import { SfTextarea } from 'qwik-storefront-ui'; | ||
|
||
export default component$(() => { | ||
const characterLimit = 25; | ||
const disabled = false; | ||
const readonly = false; | ||
const invalid = false; | ||
const helpText = 'Help text'; | ||
const errorText = 'Error'; | ||
|
||
const valueSignal = useSignal(''); | ||
|
||
const isAboveLimitSignal = useComputed$(() => | ||
characterLimit ? valueSignal.value.length > characterLimit : false | ||
); | ||
|
||
const charsCountSignal = useComputed$(() => | ||
characterLimit ? characterLimit - valueSignal.value.length : null | ||
); | ||
|
||
const getCharacterLimitClass = () => | ||
isAboveLimitSignal.value | ||
? 'text-negative-700 font-medium' | ||
: 'text-neutral-500'; | ||
|
||
const onInput = $((e: Event) => { | ||
const target = e.target as HTMLInputElement; | ||
valueSignal.value = target.value; | ||
}); | ||
|
||
return ( | ||
<> | ||
<label> | ||
<span class="text-sm font-medium">Description</span> | ||
<SfTextarea | ||
value={valueSignal.value} | ||
placeholder="Write something about yourself..." | ||
invalid={invalid} | ||
disabled={disabled} | ||
onInput$={onInput} | ||
wrapperClass={['w-full mt-0.5 block']} | ||
/> | ||
</label> | ||
<div class="flex justify-between mt-0.5"> | ||
<div> | ||
{invalid && !disabled && ( | ||
<p class="typography-text-sm text-negative-700 font-medium mt-0.5"> | ||
{errorText} | ||
</p> | ||
)} | ||
{helpText && ( | ||
<p | ||
class={[ | ||
'typography-hint-xs', | ||
disabled ? 'text-disabled-500' : 'text-neutral-500', | ||
]} | ||
> | ||
{helpText} | ||
</p> | ||
)} | ||
</div> | ||
{characterLimit && !readonly ? ( | ||
<p | ||
class={[ | ||
'typography-error-xs', | ||
disabled ? 'text-disabled-500' : getCharacterLimitClass(), | ||
]} | ||
> | ||
{charsCountSignal.value} | ||
</p> | ||
) : null} | ||
</div> | ||
</> | ||
); | ||
}); |
24 changes: 24 additions & 0 deletions
24
apps/website/src/routes/showcases/Textarea/TextareaDisabled/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { component$ } from '@builder.io/qwik'; | ||
import { SfTextarea } from 'qwik-storefront-ui'; | ||
|
||
export default component$(() => { | ||
return ( | ||
<> | ||
<label> | ||
<span class="typography-text-sm font-medium cursor-not-allowed text-disabled-900"> | ||
Description | ||
</span> | ||
<SfTextarea | ||
disabled | ||
placeholder="Write something about yourself..." | ||
wrapperClass={[ | ||
'w-full !bg-disabled-100 !ring-disabled-300 !ring-1 block', | ||
]} | ||
/> | ||
</label> | ||
<p class="typography-hint-xs text-disabled-500 mt-0.5"> | ||
Do not include personal or financial information. | ||
</p> | ||
</> | ||
); | ||
}); |
25 changes: 25 additions & 0 deletions
25
apps/website/src/routes/showcases/Textarea/TextareaInvalid/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { component$ } from '@builder.io/qwik'; | ||
import { SfTextarea } from 'qwik-storefront-ui'; | ||
|
||
export default component$(() => { | ||
return ( | ||
<> | ||
<label> | ||
<span class="typography-text-sm font-medium">Description</span> | ||
<SfTextarea | ||
invalid | ||
placeholder="Write something about yourself..." | ||
wrapperClass={['w-full block']} | ||
/> | ||
</label> | ||
<div class="flex justify-between mt-0.5"> | ||
<p class="typography-text-sm text-negative-700 font-medium"> | ||
The field cannot be empty | ||
</p> | ||
<p class="typography-hint-xs text-neutral-500"> | ||
Do not include personal or financial information. | ||
</p> | ||
</div> | ||
</> | ||
); | ||
}); |
22 changes: 22 additions & 0 deletions
22
apps/website/src/routes/showcases/Textarea/TextareaReadonly/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { component$ } from '@builder.io/qwik'; | ||
import { SfTextarea } from 'qwik-storefront-ui'; | ||
|
||
export default component$(() => { | ||
return ( | ||
<> | ||
<label> | ||
<span class="typography-text-sm font-medium">Description</span> | ||
<SfTextarea | ||
value="Hello! I'm a passionate shopper and a regular user of this ecommerce platform." | ||
wrapperClass={[ | ||
'w-full !bg-disabled-100 !ring-disabled-300 !ring-1 block', | ||
]} | ||
readOnly | ||
/> | ||
</label> | ||
<p class="typography-hint-xs text-neutral-500 mt-0.5"> | ||
Do not include personal or financial information. | ||
</p> | ||
</> | ||
); | ||
}); |
Oops, something went wrong.