Skip to content

Commit

Permalink
feat: add SfTextarea component (#30)
Browse files Browse the repository at this point in the history
* 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
thecoder93 authored Oct 26, 2023
1 parent 9cf058f commit 532fc27
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 2 deletions.
2 changes: 1 addition & 1 deletion apps/docs/utils/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const components = {
// 'SfScrollable',
// 'SfSelect',
'SfSwitch',
// 'SfTextarea',
'SfTextarea',
// 'SfThumbnail',
// 'SfTooltip',
],
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/utils/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const hooks = [
// 'useDisclosure',
// 'useDropdown',
// 'useFocusVisible',
'useFocusVisible',
// 'usePagination',
// 'usePopover',
// 'useScrollable',
Expand Down
197 changes: 197 additions & 0 deletions apps/website/src/routes/examples/SfTextarea/index.tsx
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>
);
});
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>
</>
);
});
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>
</>
);
});
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>
</>
);
});
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>
</>
);
});
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>
</>
);
});
Loading

0 comments on commit 532fc27

Please sign in to comment.