diff --git a/src/components/GInput/GInput.js b/src/components/GInput/GInput.js deleted file mode 100644 index c7f0ee0f..00000000 --- a/src/components/GInput/GInput.js +++ /dev/null @@ -1,79 +0,0 @@ -//todo: setup return function (even handler, slot generate) return props class (use getVModel) -// todo: lazy value, internalValue, common event handler, watcher - -import {computed, createElement, ref, watch} from '@vue/composition-api'; -import getVModel from '../../mixins/getVModel'; -import _ from 'lodash'; - -const props = { - label: String, - disabled: Boolean, - readOnly: Boolean, - value: null, - rules: Function, - appendIcon: String, - prependIcon: String, - hasMouseDown: Boolean -} - -export default function getGInput(props, context) { - - const state = { - hasMouseDown: false, - } - // let lazyValue = ref(props.value); - // let isValidInput = ref(false); - - //todo: internal value, watcher to validate content - //event listen function - function onClick(event) { - context.emit('click', event) - } - - function onMouseDown(event) { - state.hasMouseDown = true - context.emit('mousedown', event) - } - - function onMouseUp(event) { - if (state.hasMouseDown) { - state.hasMouseDown = false - context.emit('mouseup', event) - } - } - - function onChange(event) { - context.emit('change', event) - } - - // function onInput(event) { - // internalValue.value = event.target.value; - // } - - const listeners = { - onClick, - onMouseDown, - onMouseUp, - onChange, - // onInput, - } - - //computed state - const isDisabled = computed(() => props.disable) - const hasLabel = computed(() => props.label || context.slots.label) - // todo: move to field input mixin - // let isLabelActive = computed() - - - return { - listeners, - state, - isDisabled, - hasLabel, - // isDirty, - // isValidInput, - // lazyValue - } - - -} diff --git a/src/components/GInput/GInputFactory.js b/src/components/GInput/GInputFactory.js index 068a32e1..f882601e 100644 --- a/src/components/GInput/GInputFactory.js +++ b/src/components/GInput/GInputFactory.js @@ -1,7 +1,11 @@ export function getLabel(context, props, internalValue, isValidInput, isFocused, - labelActiveClass = 'g-tf-label__active') { + [labelActiveClass] = 'g-tf-label__active') { + const tfType = computed(() => { + if (context.slots['prepend-outer'] || props.prependIcon || props.outlined) return 'full' + return 'lite' + }) //Activate label - const isDirty = computed(() => !!internalValue.value) + const isDirty = computed(() => !!internalValue.value || internalValue.value === 0) const isLabelActive = computed(() => { const datetimeInputTypes = ['date', 'datetime', 'datetime-local', 'month', 'time', 'week'] @@ -22,6 +26,19 @@ export function getLabel(context, props, internalValue, isValidInput, isFocused, const prefixWidth = reactive({ value: 0 }) + const prependRef = ref(null) + const prependWidth = ref(0) + // const prependWidth = computed(() => { + // let i = props.filled + // console.log(prependRef.value && prependRef.value.offsetWidth) + // return prependRef.value && prependRef.value.offsetWidth + // }) + watch([() => prependRef.value, () => props.filled], () => { + context.root.$nextTick(() => { + prependWidth.value = prependRef.value && prependRef.value.offsetWidth + }) + }) + watch(() => props.prefix, () => { context.root.$nextTick(() => { @@ -30,29 +47,55 @@ export function getLabel(context, props, internalValue, isValidInput, isFocused, }) const labelStyles = computed(() => { - if (isLabelActive.value && prefixWidth.value) { - if (props.outlined) { - if (props.filled) { - if (props.rounded) return {'transform': `translateY(-28px) translateX(${-prefixWidth.value}px) scale(0.75)`} - return {'transform': `translateY(-${props.dense ? 26 : 28}px) translateX(${-prefixWidth.value - 6}px) scale(0.75)`} + if (tfType.value === 'full') { + if (isLabelActive.value && prefixWidth.value) { + if (props.outlined) { + if (props.filled) { + if (props.rounded) return { 'transform': `translateY(-28px) translateX(${-prefixWidth.value}px) scale(0.75)` } + return { 'transform': `translateY(-${props.dense ? 26 : 28}px) translateX(${-prefixWidth.value - 6}px) scale(0.75)` } + } else { + return { 'transform': `translateY(-${props.dense ? 22 : 26}px) translateX(${-prefixWidth.value + 6}px) scale(0.75)` } + } + } else if (props.filled) { + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${-prefixWidth.value - 6}px) scale(0.75)` } } else { - return {'transform': `translateY(-${props.dense ? 22 : 26}px) translateX(${-prefixWidth.value + 6}px) scale(0.75)`} + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${-prefixWidth.value + 6}px) scale(0.75)` } } - } else if (props.filled) { - return {'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${-prefixWidth.value - 6}px) scale(0.75)`} - } else { - return {'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${-prefixWidth.value + 6}px) scale(0.75)`} } + } else { + if (!isLabelActive.value && (prependWidth.value || prefixWidth.value)) { + if(props.filled) { + if(props.rounded || props.shaped ) return { 'padding-left': `${prependWidth.value + prefixWidth.value -5}px` } + return { 'padding-left': `${prependWidth.value + prefixWidth.value}px` }} + return { 'padding-left': `${prependWidth.value + prefixWidth.value -1}px` } + } + if (isLabelActive.value && (prependWidth.value || prefixWidth.value)) { + if (!prefixWidth.value) { + if(props.filled) { + if(props.rounded||props.shaped)return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value- 5}px) scale(0.75)` } + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value}px) scale(0.75)` }} + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value}px) scale(0.75)` } + } + else { + if (props.filled) { + if (props.rounded) return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value - 4}px) scale(0.75)` } + if (props.shaped) return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value - 10}px) scale(0.75)` } + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value -6}px) scale(0.75)` } + } + return { 'transform': `translateY(-${props.dense ? 12 : 16}px) translateX(${prependWidth.value + 6}px) scale(0.75)` } + } + } + } }) - return {labelClasses, labelStyles, isDirty, isLabelActive, prefixRef} + return { labelClasses, labelStyles, isDirty, isLabelActive, prefixRef, prependRef } } -import {computed, reactive, ref, watch} from '@vue/composition-api'; +import { computed, reactive, ref, watch } from '@vue/composition-api'; -import {convertToUnit, keyCodes} from '../../utils/helpers'; +import { convertToUnit, keyCodes } from '../../utils/helpers'; export function getValidate(props, isFocused, internalValue, isValidInput, customAlert) { //Validation @@ -92,9 +135,9 @@ export function getValidate(props, isFocused, internalValue, isValidInput, custo isValidInput.value = true } - }, !props.value ? {lazy: true} : null) + }, !props.value ? { lazy: true } : null) - return {errorMessages, validate}; + return { errorMessages, validate }; } export function getSlotEventListeners(context) { @@ -185,14 +228,14 @@ export function getEvents(props, context, internalValue, isFocused, isValidInput } } - return {onClick, onFocus, onBlur, onClearIconClick, onMouseDown, onMouseUp, onChange, onKeyDown} + return { onClick, onFocus, onBlur, onClearIconClick, onMouseDown, onMouseUp, onChange, onKeyDown } } export function getInternalValue(props, context) { // text field internalValue - const rawInternalValue = ref(props.value || ''); + const rawInternalValue = ref((props.value || props.value === 0) ? props.value : ''); - watch(() => props.value, () => rawInternalValue.value = props.value, {lazy: true}); + watch(() => props.value, () => rawInternalValue.value = props.value, { lazy: true }); const internalValue = computed({ @@ -203,5 +246,5 @@ export function getInternalValue(props, context) { } }); - return {internalValue, rawInternalValue}; + return { internalValue, rawInternalValue }; } diff --git a/src/components/GInput/GTextField.vue b/src/components/GInput/GTextField.vue index 657600b8..742b0022 100644 --- a/src/components/GInput/GTextField.vue +++ b/src/components/GInput/GTextField.vue @@ -1,86 +1,143 @@ - - - - {{prependIcon}} - - - - {{label}} - - - - {{prependInnerIcon}} - - - {{prefix}} - - - - - - - - {{label}} - * - - - - {{suffix}} - - - {{clearIcon}} - - - - {{appendInnerIcon}} - - - - {{errorMessages}} - - {{hint}} - - - {{internalValue.length}} / {{counter}} - - - - - - - - {{appendIcon}} - - - - - + + + + {{prependIcon}} + + + + {{label}} + + + + {{prependInnerIcon}} + + + {{prefix}} + + + + + + + + {{label}} + * + + + + {{suffix}} + + + {{clearIcon}} + + + + {{appendInnerIcon}} + + + + {{errorMessages}} + + {{hint}} + + + {{internalValue.toString().length}} / {{counter}} + + + + + + + + {{appendIcon}} + + + + + + + + {{prependInnerIcon}} + + + {{prefix}} + + + + + {{label}} + * + + + {{suffix}} + + + + {{clearIcon}} + + + {{appendInnerIcon}} + + + + + + + {{errorMessages}} + + + + {{hint}} + + + {{internalValue.toString().length}} / {{counter}} + + + diff --git a/src/components/GInput/_GInputField.scss b/src/components/GInput/_GInputField.scss index d4495279..90f8c4a8 100644 --- a/src/components/GInput/_GInputField.scss +++ b/src/components/GInput/_GInputField.scss @@ -158,6 +158,7 @@ input[type=text] { font-size: 12px; font-weight: 400; line-height: 1.34; + opacity: 1; } &-error { @@ -174,6 +175,7 @@ input[type=text] { } } + .g-tf-prepend__outer, .g-tf-append__outer { display: flex; @@ -201,6 +203,9 @@ input[type=text] { max-width: 48px; max-height: 48px; } + .g-icon{ + width: 24px; + } } .g-tf-prepend__inner { @@ -558,6 +563,7 @@ input[type=text] { .g-tf__rounded { &.g-tf__solo .g-tf, + &.g-tf__solo.g-tf, &.g-tf__outlined .g-tf { border-radius: 9999px; @@ -567,10 +573,11 @@ input[type=text] { } } - &.g-tf__filled .g-tf { - border-radius: 9999px; + &.g-tf__filled { + .g-tf, &.g-tf{border-radius: 9999px;} } + &.g-tf__solo .g-tf-input { margin: 0 4px; } @@ -678,7 +685,9 @@ input[type=text] { transform: translateY(-29px) translateX(6px) scale(0.75); } - &.g-tf__filled .g-tf { + &.g-tf__filled .g-tf, + &.g-tf__filled.g-tf + { border-top-left-radius: 16px; border-top-right-radius: 16px; } @@ -950,3 +959,24 @@ input[type=text] { } } } + +//lite text field +.g-tf__solo{ + &.g-tf{ + border-radius: 4px; + @include elevation(2);} + &.g-tf::before, + &.g-tf::after{ + display: none; + } +} +.g-tf__filled{ + &.g-tf{ + background-color: rgba(0, 0, 0, 0.06); + border-top-left-radius: 4px; + border-top-right-radius: 4px;} + &.g-tf::before, + &.g-tf::after{ + display: none; + } +} diff --git a/src/components/GInput/__story__/GTextField.stories.js b/src/components/GInput/__story__/GTextField.stories.js index 51525127..ba8e8482 100644 --- a/src/components/GInput/__story__/GTextField.stories.js +++ b/src/components/GInput/__story__/GTextField.stories.js @@ -8,8 +8,7 @@ export default { title: 'GTextField', decorators: [withKnobs], } - -export const TextFieldPlayGround = () => ({ +export const TextFieldPlayGroundFull = () => ({ components: {GTextField, GIcon}, data() { return { @@ -29,13 +28,13 @@ export const TextFieldPlayGround = () => ({ props: { label: {default: text('Input label', 'Label')}, placeholder: {default: text('Input placeholder', '')}, - filled: {default: boolean('filled', false)}, + filled: {default: boolean('filled', true)}, solo: {default: boolean('solo', false)}, outlined: {default: boolean('outlined', false)}, flat: {default: boolean('flat', false)}, dense: {default: boolean('dense', false)}, rounded: {default: boolean('rounded', false)}, - shaped: {default: boolean('shaped', false)}, + shaped: {default: boolean('shaped', true)}, clearable: {default: boolean('clearable', false)}, hint: {default: text('hint', 'Hint')}, persistent: {default: boolean('persistent', false)}, @@ -44,7 +43,7 @@ export const TextFieldPlayGround = () => ({ suffix: {default: text('suffix', '')}, type: {default: text('type', 'text')}, prependIcon: {default: text('prepend Icon', 'person')}, - appendIcon: {default: text('append Icon', 'mdi-bike')}, + appendIcon: {default: text('append Icon', 'check')}, prependInnerIcon: {default: text('prepend Inner Icon', 'mdi-glasses')}, appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, }, @@ -76,9 +75,75 @@ export const TextFieldPlayGround = () => ({ {{appendInnerIcon}} + asdas `, }); +export const TextFieldPlayGroundLite = () => ({ + components: {GTextField, GIcon}, + data() { + return { + text1: '', + rules: { + + required: value => !!value || 'Required', + counter: value => value.length > 4 || 'Min 5 characters', + email: value => { + const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return pattern.test(value) || 'Invalid e-mail' + } + }, + mask: 'XXXX-XXXX', + } + }, + props: { + label: {default: text('Input label', 'Label')}, + placeholder: {default: text('Input placeholder', '')}, + filled: {default: boolean('filled', true)}, + solo: {default: boolean('solo', false)}, + outlined: {default: boolean('outlined', false)}, + flat: {default: boolean('flat', false)}, + dense: {default: boolean('dense', true)}, + rounded: {default: boolean('rounded', false)}, + shaped: {default: boolean('shaped', false)}, + clearable: {default: boolean('clearable', false)}, + hint: {default: text('hint', 'Hint')}, + persistent: {default: boolean('persistent', false)}, + counter: {type: [String, Number], default: number('counter', 25)}, + prefix: {default: text('prefix', 'prefix')}, + suffix: {default: text('suffix', '')}, + type: {default: text('type', 'text')}, + prependIcon: {default: text('prepend Icon', '')}, + appendIcon: {default: text('append Icon', '')}, + prependInnerIcon: {default: text('prepend Inner Icon', 'mdi-glasses')}, + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + }, + template: ` + + + `, +}); export const TextFieldShapedAndRounded = () => ({ components: {GTextField, GContainer, GRow, GCol}, props: { @@ -397,7 +462,7 @@ export const TextFieldPrefixAndSuffix = () => ({ components: {GTextField, GIcon}, props: { label: {default: text('label', 'Label')}, - filled: {default: boolean('filled', false)}, + filled: {default: boolean('filled', true)}, solo: {default: boolean('solo', false)}, outlined: {default: boolean('outlined', false)}, flat: {default: boolean('flat', false)}, @@ -414,7 +479,9 @@ export const TextFieldPrefixAndSuffix = () => ({ :rounded="rounded" :shaped="shaped" :prefix="prefix" - :suffix="suffix"> + :suffix="suffix" + appendIcon="check" + > `, }); //todo: icon event, icon slot @@ -552,7 +619,7 @@ export const TextFieldValidate = () => ({ clearable: {default: boolean('clearable', false)}, }, template: ` ({ + components: {GTextField, GIcon}, + data() { + return { + text1: text1, + rules: { + + required: value => !!value || 'Required', + counter: value => value.length > 4 || 'Min 5 characters', + email: value => { + const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return pattern.test(value) || 'Invalid e-mail' + } + }, + mask: 'XXXX-XXXX', + } + }, + props: props, + template: ` + `, +}); +describe('render lite input test:', function () { + it('should render full textfield', function () { + const props = { + label: {default: text('Input label', 'Label')}, + placeholder: {default: text('Input placeholder', '')}, + filled: {default: boolean('filled', false)}, + solo: {default: boolean('solo', false)}, + outlined: {default: boolean('outlined', false)}, + flat: {default: boolean('flat', false)}, + dense: {default: boolean('dense', false)}, + rounded: {default: boolean('rounded', false)}, + shaped: {default: boolean('shaped', false)}, + clearable: {default: boolean('clearable', false)}, + hint: {default: text('hint', 'Hint')}, + persistent: {default: boolean('persistent', false)}, + counter: {type: [String, Number], default: number('counter', 25)}, + prefix: {default: text('prefix', '')}, + suffix: {default: text('suffix', '')}, + type: {default: text('type', 'text')}, + prependIcon: {default: text('prepend Icon', 'person')}, + appendIcon: {default: text('append Icon', 'mdi-bike')}, + prependInnerIcon: {default: text('prepend Inner Icon', 'mdi-glasses')}, + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + } + const vm = new Vue(TextField(props, '')).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + it('should render lite textfield with wrapper', function () { + const props = { + label: {default: text('Input label', 'Label')}, + placeholder: {default: text('Input placeholder', '')}, + filled: {default: boolean('filled', false)}, + solo: {default: boolean('solo', false)}, + outlined: {default: boolean('outlined', false)}, + flat: {default: boolean('flat', false)}, + dense: {default: boolean('dense', false)}, + rounded: {default: boolean('rounded', false)}, + shaped: {default: boolean('shaped', false)}, + clearable: {default: boolean('clearable', false)}, + hint: {default: text('hint', 'Hint')}, + persistent: {default: boolean('persistent', false)}, + counter: {type: [String, Number], default: number('counter', 25)}, + prefix: {default: text('prefix', '')}, + suffix: {default: text('suffix', '')}, + type: {default: text('type', 'text')}, + prependInnerIcon: {default: text('prepend Inner Icon', 'mdi-glasses')}, + appendIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + } + const vm = new Vue(TextField(props, '')).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + + it('should render full textfield', function () { + const vm = new Vue(TextFieldShapedAndRounded()).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + it('should render lite textfield without wrapper and validate wrong value', function () { + const props = { + label: {default: 'Label'}, + counter: {default: 10}, + placeholder: {default: 'Input placeholder'}, + hint: {default: text('hint', 'Hint')}, + prependInnerIcon: {default: text('prepend Inner Icon', '')}, + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + } + const vm = new Vue(TextField(props, '1')).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + it('should render lite textfield and not render label', function () { + const props = { + label: {default: 'Label'}, + solo: {default: true}, + counter: {default: 10}, + placeholder: {default: 'Input placeholder'}, + hint: {default: text('hint', 'Hint')}, + prependInnerIcon: {default: text('prepend Inner Icon', 'mdi-glasses')}, + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + } + const vm = new Vue(TextField(props, '1')).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + + it('should render full textfield outer slot', function () { + const vm = new Vue({ + components: {GTextField, GIcon}, + props: { + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + }, + template: ` + + + + {{appendInnerIcon}} + + + + `, + }).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + it('should render lite textfield without wrapper inner slot', function () { + const vm = new Vue({ + components: {GTextField, GIcon}, + props: { + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + }, + template: ` + + {{appendInnerIcon}} + + `, + }).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); + it('should render lite textfield with wrapper append outer slot', function () { + const vm = new Vue({ + components: {GTextField, GIcon}, + props: { + appendInnerIcon: {default: text('append Inner Icon', 'mdi-ninja')}, + }, + template: ` + + {{appendInnerIcon}} + + `, + }).$mount(); + expect(vm.$el.outerHTML).toMatchSnapshot() + }); -describe('test', function () { - it('should', function () { - const vm = new Vue(test1()).$mount(); - // your expect here - }) }) diff --git a/src/components/GSelect/GSelect.vue b/src/components/GSelect/GSelect.vue index e1830a2f..3abe3e77 100644 --- a/src/components/GSelect/GSelect.vue +++ b/src/components/GSelect/GSelect.vue @@ -243,7 +243,7 @@ ..._.pick(props, ['filled', 'solo', 'outlined', 'flat', 'rounded', 'shaped', 'clearable', 'hint', 'persistent', 'counter', 'placeholder', 'label', 'prefix', 'suffix', 'rules', 'type', 'disabled', 'readOnly', 'required']), - value: tfValue.value + value: tfValue.value, }, on: { 'click:clearIcon': () => clearSelection(),