Skip to content

Commit

Permalink
feat(input-textarea): add preview functionality (#471)
Browse files Browse the repository at this point in the history
Co-authored-by: Kia King Ishii <kia.king.08@gmail.com>
  • Loading branch information
brc-dd and kiaking committed Feb 19, 2024
1 parent 9519593 commit 1510d12
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 56 deletions.
164 changes: 112 additions & 52 deletions lib/components/SInputTextarea.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { type IconifyIcon } from '@iconify/vue/dist/offline'
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { type Validatable } from '../composables/V'
import SInputBase from './SInputBase.vue'
import SInputSegments from './SInputSegments.vue'
export type Size = 'mini' | 'small' | 'medium'
export type Color = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
Expand All @@ -24,6 +25,9 @@ const props = defineProps<{
modelValue?: string | null
hideError?: boolean
validation?: Validatable
preview?: (value: string | null) => string
previewLabel?: string
writeLabel?: string
}>()
const emit = defineEmits<{
Expand Down Expand Up @@ -57,6 +61,8 @@ function emitBlur(e: FocusEvent): void {
emit('update:model-value', v)
emit('blur', v)
}
const isPreview = ref(false)
</script>

<template>
Expand All @@ -74,24 +80,96 @@ function emitBlur(e: FocusEvent): void {
:hide-error="hideError"
:validation="validation"
>
<textarea
:id="name"
class="input"
:class="{ fill: rows === 'fill' }"
:placeholder="placeholder"
:rows="rows ?? 3"
:disabled="disabled"
:value="_value ?? undefined"
@input="emitInput"
@blur="emitBlur"
/>
<div class="box">
<div v-if="preview !== undefined" class="control">
<SInputSegments
:options="[
{ label: writeLabel ?? 'Write', value: false },
{ label: previewLabel ?? 'Preview', value: true }
]"
v-model="isPreview"
size="mini"
/>
</div>
<textarea
v-show="!isPreview"
:id="name"
class="input"
:placeholder="placeholder"
:rows="rows ?? 3"
:disabled="disabled"
:value="_value ?? undefined"
@input="emitInput"
@blur="emitBlur"
/>
<div
v-if="preview !== undefined && isPreview"
class="prose"
:class="!_value && 'empty'"
v-html="preview(_value)"
/>
</div>
<template v-if="$slots.info" #info><slot name="info" /></template>
</SInputBase>
</template>
<style scoped lang="postcss">
.box {
display: flex;
flex-direction: column;
gap: 1px;
flex-grow: 1;
border: 1px solid var(--input-border-color);
border-radius: 6px;
width: 100%;
background-color: var(--c-gutter);
overflow: hidden;
transition: border-color 0.25s;
&:has(.input:hover) {
border-color: var(--input-hover-border-color);
}
&:has(.input:focus) {
border-color: var(--input-focus-border-color);
}
}
.control {
display: flex;
align-items: center;
padding: 0 8px;
height: 48px;
background-color: var(--c-bg-elv-3);
}
.input,
.prose {
display: block;
flex-grow: 1;
width: 100%;
font-family: var(--input-value-font-family);
font-weight: 400;
background-color: var(--input-bg-color);
}
.input {
&::placeholder {
color: var(--input-placeholder-color);
}
}
.prose {
background-color: var(--c-bg-elv-3);
&.empty {
color: var(--input-placeholder-color);
}
}
.SInputTextarea.mini {
.input {
.input,
.prose {
padding: 6px 10px;
width: 100%;
min-height: 80px;
Expand All @@ -101,7 +179,8 @@ function emitBlur(e: FocusEvent): void {
}
.SInputTextarea.small {
.input {
.input,
.prose {
padding: 7px 12px;
width: 100%;
min-height: 96px;
Expand All @@ -111,7 +190,8 @@ function emitBlur(e: FocusEvent): void {
}
.SInputTextarea.medium {
.input {
.input,
.prose {
padding: 11px 16px;
width: 100%;
min-height: 96px;
Expand All @@ -120,53 +200,33 @@ function emitBlur(e: FocusEvent): void {
}
}
.SInputTextarea.disabled {
.SInputTextarea.fill {
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
.input,
.input:hover {
.prose {
height: 100%;
}
}
.SInputTextarea.disabled {
.box {
border-color: var(--input-disabled-border-color);
}
.input {
color: var(--input-disabled-value-color);
background-color: var(--input-disabled-bg-color);
cursor: not-allowed;
}
}
.SInputTextarea.has-error {
.input {
.box {
border-color: var(--input-error-border-color);
&:hover,
&:focus {
border-color: var(--input-error-border-color);
}
}
}
.input {
display: block;
flex-grow: 1;
border: 1px solid var(--input-border-color);
border-radius: 6px;
width: 100%;
font-family: var(--input-value-font-family);
font-weight: 400;
background-color: var(--input-bg-color);
transition: border-color 0.25s, background-color 0.25s;
&::placeholder {
color: var(--input-placeholder-color);
}
&:hover {
border-color: var(--input-hover-border-color);
}
&:focus,
&:hover:focus {
border-color: var(--input-focus-border-color);
}
&.fill {
height: 100%;
}
}
</style>
1 change: 1 addition & 0 deletions lib/composables/Markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface UseMarkdownOptions extends MarkdownIt.Options {

export function useMarkdown(options: UseMarkdownOptions = {}): UseMarkdown {
const md = new MarkdownIt({
html: true,
linkify: true,
...options
})
Expand Down
8 changes: 4 additions & 4 deletions lib/styles/bootstrap.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "normalize.css";
@import "v-calendar/dist/style.css";
@import "./variables-deprecated";
@import "./variables";
@import "./base";
@import "./utilities";
@import "./variables-deprecated.css";
@import "./variables.css";
@import "./base.css";
@import "./utilities.css";
97 changes: 97 additions & 0 deletions stories/components/SInputTextarea.02_Preview.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<script setup lang="ts">
import SInputTextarea from 'sefirot/components/SInputTextarea.vue'
import { useMarkdown } from 'sefirot/composables/Markdown'
import { ref } from 'vue'
const title = 'Components / SInputTextarea / 02. Preview'
const docs = '/components/input-textarea'
const text = ref('')
function state() {
return {
size: 'small',
label: 'Label',
info: 'Some helpful information.',
note: 'Note text',
placeholder: 'Placeholder text',
help: 'This is a help text.',
rows: 5,
disabled: false,
error: false
}
}
const render = useMarkdown()
function preview() {
if (!text.value) { return 'Nothing to preview' }
return render(text.value, false)
}
</script>

<template>
<Story :title="title" :init-state="state" source="Not available" auto-props-disabled>
<template #controls="{ state }">
<HstSelect
title="size"
:options="{
mini: 'mini',
small: 'small',
medium: 'medium'
}"
v-model="state.size"
/>
<HstText
title="label"
v-model="state.label"
/>
<HstText
title="info"
v-model="state.info"
/>
<HstText
title="note"
v-model="state.note"
/>
<HstText
title="placeholder"
v-model="state.placeholder"
/>
<HstText
title="help"
v-model="state.help"
/>
<HstNumber
title="rows"
v-model="state.rows"
/>
<HstCheckbox
title="disabled"
v-model="state.disabled"
/>
<HstCheckbox
title="error"
v-model="state.error"
/>
</template>

<template #default="{ state }">
<Board :title="title" :docs="docs">
<SInputTextarea
:class="{ 'has-error': state.error }"
:size="state.size"
:label="state.label"
:info="state.info"
:note="state.note"
:help="state.help"
:placeholder="state.placeholder"
:rows="state.rows"
:disabled="state.disabled"
v-model="text"
:preview="preview"
/>
</Board>
</template>
</Story>
</template>

0 comments on commit 1510d12

Please sign in to comment.