Skip to content

Commit

Permalink
Add TextArea size calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
rjborba committed Jul 4, 2024
1 parent 00b1d69 commit 59fd258
Show file tree
Hide file tree
Showing 10 changed files with 12,446 additions and 10,283 deletions.
17 changes: 17 additions & 0 deletions apps/storybook/stories/textarea.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StoryObj, Meta } from '@storybook/html'

const meta = {
title: 'Internal/Form',
tags: ['autodocs']
} satisfies Meta

export default meta
type Story = StoryObj

export const TextArea: Story = {
render: (args) =>
`<orama-textarea name='test1' label='Small size input' size='small' placeholder='Blablablbal' max-rows='5'></orama-textarea>`,
args: {
placeholder: 'Name'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ export class OramaSpan {
export declare interface OramaSpan extends Components.OramaSpan {}


@ProxyCmp({
inputs: ['maxRows', 'minRows', 'placeholder', 'value']
})
@Component({
selector: 'orama-textarea',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['maxRows', 'minRows', 'placeholder', 'value'],
})
export class OramaTextarea {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}


export declare interface OramaTextarea extends Components.OramaTextarea {}


@ProxyCmp({
inputs: ['color', 'themeConfig']
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const DIRECTIVES = [
d.OramaParagraph,
d.OramaSmall,
d.OramaSpan,
d.OramaTextarea,
d.SearchBox
];
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export const OramaInput = /*@__PURE__*/createReactComponent<JSX.OramaInput, HTML
export const OramaParagraph = /*@__PURE__*/createReactComponent<JSX.OramaParagraph, HTMLOramaParagraphElement>('orama-paragraph');
export const OramaSmall = /*@__PURE__*/createReactComponent<JSX.OramaSmall, HTMLOramaSmallElement>('orama-small');
export const OramaSpan = /*@__PURE__*/createReactComponent<JSX.OramaSpan, HTMLOramaSpanElement>('orama-span');
export const OramaTextarea = /*@__PURE__*/createReactComponent<JSX.OramaTextarea, HTMLOramaTextareaElement>('orama-textarea');
export const SearchBox = /*@__PURE__*/createReactComponent<JSX.SearchBox, HTMLSearchBoxElement>('search-box');
8 changes: 8 additions & 0 deletions packages/ui-stencil-vue/lib/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export const OramaSpan = /*@__PURE__*/ defineContainer<JSX.OramaSpan>('orama-spa
]);


export const OramaTextarea = /*@__PURE__*/ defineContainer<JSX.OramaTextarea>('orama-textarea', undefined, [
'value',
'maxRows',
'minRows',
'placeholder'
]);


export const SearchBox = /*@__PURE__*/ defineContainer<JSX.SearchBox>('search-box', undefined, [
'themeConfig',
'color'
Expand Down
21 changes: 21 additions & 0 deletions packages/ui-stencil/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export namespace Components {
interface OramaSpan {
"as"?: SpanProps['as'];
}
interface OramaTextarea {
"maxRows": number | string;
"minRows": number | string;
"placeholder": string;
"value": string | null;
}
interface SearchBox {
"color": 'dark' | 'light' | 'system';
"themeConfig": { colors: { light: { primaryColor: string }; dark: { primaryColor: string } } };
Expand Down Expand Up @@ -68,6 +74,12 @@ declare global {
prototype: HTMLOramaSpanElement;
new (): HTMLOramaSpanElement;
};
interface HTMLOramaTextareaElement extends Components.OramaTextarea, HTMLStencilElement {
}
var HTMLOramaTextareaElement: {
prototype: HTMLOramaTextareaElement;
new (): HTMLOramaTextareaElement;
};
interface HTMLSearchBoxElement extends Components.SearchBox, HTMLStencilElement {
}
var HTMLSearchBoxElement: {
Expand All @@ -80,6 +92,7 @@ declare global {
"orama-paragraph": HTMLOramaParagraphElement;
"orama-small": HTMLOramaSmallElement;
"orama-span": HTMLOramaSpanElement;
"orama-textarea": HTMLOramaTextareaElement;
"search-box": HTMLSearchBoxElement;
}
}
Expand All @@ -102,6 +115,12 @@ declare namespace LocalJSX {
interface OramaSpan {
"as"?: SpanProps['as'];
}
interface OramaTextarea {
"maxRows"?: number | string;
"minRows"?: number | string;
"placeholder"?: string;
"value"?: string | null;
}
interface SearchBox {
"color"?: 'dark' | 'light' | 'system';
"themeConfig"?: { colors: { light: { primaryColor: string }; dark: { primaryColor: string } } };
Expand All @@ -112,6 +131,7 @@ declare namespace LocalJSX {
"orama-paragraph": OramaParagraph;
"orama-small": OramaSmall;
"orama-span": OramaSpan;
"orama-textarea": OramaTextarea;
"search-box": SearchBox;
}
}
Expand All @@ -124,6 +144,7 @@ declare module "@stencil/core" {
"orama-paragraph": LocalJSX.OramaParagraph & JSXBase.HTMLAttributes<HTMLOramaParagraphElement>;
"orama-small": LocalJSX.OramaSmall & JSXBase.HTMLAttributes<HTMLOramaSmallElement>;
"orama-span": LocalJSX.OramaSpan & JSXBase.HTMLAttributes<HTMLOramaSpanElement>;
"orama-textarea": LocalJSX.OramaTextarea & JSXBase.HTMLAttributes<HTMLOramaTextareaElement>;
"search-box": LocalJSX.SearchBox & JSXBase.HTMLAttributes<HTMLSearchBoxElement>;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:host {
display: block;
}

textarea {
resize: none
}
148 changes: 148 additions & 0 deletions packages/ui-stencil/src/components/orama-textarea/orama-textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Component, Host, Prop, State, Watch, h, Element } from '@stencil/core'

@Component({
tag: 'orama-textarea',
styleUrl: 'orama-textarea.scss',
shadow: true
})
export class OramaTextarea {
@Element() el: HTMLElement

@Prop() value: string | null = ''
@Prop() maxRows: number | string
@Prop() minRows: number | string = 1
@Prop() placeholder: string

@State() height: number

textarea!: HTMLTextAreaElement
shadowTextarea!: HTMLTextAreaElement

componentDidLoad() {
this.syncHeight()
}

@Watch('value')
@Watch('maxRows')
@Watch('minRows')
handlePropsChange() {
this.syncHeight()
}

getStyleValue(value: string) {
return Number.parseInt(value, 10) || 0
}

calculateTextareaStyles() {
const input = this.textarea
const computedStyle = window.getComputedStyle(input)

if (computedStyle.width === '0px') {
return {
outerHeightStyle: 0,
overflowing: false
}
}

const inputShallow = this.shadowTextarea
inputShallow.style.width = computedStyle.width
inputShallow.value = input.value || this.placeholder || 'x'
if (inputShallow.value.slice(-1) === '\n') {
inputShallow.value += ' '
}

const boxSizing = computedStyle.boxSizing
const padding = this.getStyleValue(computedStyle.paddingBottom) + this.getStyleValue(computedStyle.paddingTop)
const border =
this.getStyleValue(computedStyle.borderBottomWidth) + this.getStyleValue(computedStyle.borderTopWidth)

const innerHeight = inputShallow.scrollHeight
inputShallow.value = 'x'
const singleRowHeight = inputShallow.scrollHeight

let outerHeight = innerHeight

if (this.minRows) {
outerHeight = Math.max(Number(this.minRows) * singleRowHeight, outerHeight)
}
if (this.maxRows) {
outerHeight = Math.min(Number(this.maxRows) * singleRowHeight, outerHeight)
}
outerHeight = Math.max(outerHeight, singleRowHeight)

const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0)
const overflowing = Math.abs(outerHeight - innerHeight) <= 1

return { outerHeightStyle, overflowing }
}

syncHeight() {
const textareaStyles = this.calculateTextareaStyles()

if (!textareaStyles) {
return
}

const outerHeightStyle = textareaStyles.outerHeightStyle
if (this.height !== outerHeightStyle) {
this.height = outerHeightStyle
this.textarea.style.height = `${outerHeightStyle}px`
}
this.textarea.style.overflow = textareaStyles.overflowing ? 'hidden' : ''
}

handleChange = (event: Event) => {
const target = event.target as HTMLTextAreaElement
if (!this.value) {
this.syncHeight()
}
this.value = target.value
}

private getAllProps() {
const props = {}

for (let i = 0; i < this.el.attributes.length; i++) {
const attr = this.el.attributes[i]
props[attr.name] = attr.value
}
return props
}

render() {
console.log(this.getAllProps())
return (
<Host>
<textarea
{...this.getAllProps()}
value={this.value}
onInput={this.handleChange}
ref={(el) => (this.textarea = el as HTMLTextAreaElement)}
rows={Number(this.minRows)}
style={{ height: this.height ? `${this.height}px` : undefined }}
placeholder={this.placeholder}
/>

{/* Textare below should be hidden from the user and it's used to calculate the height of the textarea */}
{/* biome-ignore lint/a11y/noAriaHiddenOnFocusable: This component shouldn't be focusable */}
<textarea
aria-hidden="true"
readonly
ref={(el) => (this.shadowTextarea = el as HTMLTextAreaElement)}
tabindex={-1}
style={{
visibility: 'hidden',
position: 'absolute',
overflow: 'hidden',
height: '0',
top: '0',
left: '0',
transform: 'translateZ(0)',
paddingTop: '0',
paddingBottom: '0'
}}
/>
</Host>
)
}
}
20 changes: 20 additions & 0 deletions packages/ui-stencil/src/components/orama-textarea/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# orama-textarea



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| ------------- | ------------- | ----------- | ------------------ | ----------- |
| `maxRows` | `max-rows` | | `number \| string` | `undefined` |
| `minRows` | `min-rows` | | `number \| string` | `1` |
| `placeholder` | `placeholder` | | `string` | `undefined` |
| `value` | `value` | | `string` | `''` |


----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Loading

0 comments on commit 59fd258

Please sign in to comment.