diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 0e20e66f5a..b91008f718 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -35,6 +35,8 @@ import uiCard from './ui/card.vue'; import uiForm from './ui/form.vue'; import uiTextarea from './ui/textarea.vue'; import uiSwitch from './ui/switch.vue'; +import uiRadio from './ui/radio.vue'; +import uiSelect from './ui/select.vue'; Vue.component('mk-analog-clock', analogClock); Vue.component('mk-menu', menu); @@ -71,3 +73,5 @@ Vue.component('ui-card', uiCard); Vue.component('ui-form', uiForm); Vue.component('ui-textarea', uiTextarea); Vue.component('ui-switch', uiSwitch); +Vue.component('ui-radio', uiRadio); +Vue.component('ui-select', uiSelect); diff --git a/src/client/app/common/views/components/ui/radio.vue b/src/client/app/common/views/components/ui/radio.vue new file mode 100644 index 0000000000..2b7f1d9dd4 --- /dev/null +++ b/src/client/app/common/views/components/ui/radio.vue @@ -0,0 +1,120 @@ +<template> +<div + class="ui-radio" + :class="{ disabled, checked }" + :aria-checked="checked" + :aria-disabled="disabled" + @click="toggle" +> + <input type="radio" + :disabled="disabled" + > + <span class="button"> + <span></span> + </span> + <span class="label"><slot></slot></span> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + model: { + prop: 'model', + event: 'change' + }, + props: { + model: { + type: String, + required: false + }, + value: { + type: String, + required: false + }, + disabled: { + type: Boolean, + default: false + } + }, + computed: { + checked(): boolean { + return this.model === this.value; + } + }, + methods: { + toggle() { + this.$emit('change', this.value); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +root(isDark) + display inline-block + margin 32px 32px 32px 0 + cursor pointer + transition all 0.3s + + > * + user-select none + + &.disabled + opacity 0.6 + cursor not-allowed + + &.checked + > .button + border-color $theme-color + + &:after + background-color $theme-color + transform scale(1) + opacity 1 + + > input + position absolute + width 0 + height 0 + opacity 0 + margin 0 + + > .button + position absolute + width 20px + height 20px + background none + border solid 2px rgba(#000, 0.54) + border-radius 100% + transition inherit + + &:after + content '' + display block + position absolute + top 3px + right 3px + bottom 3px + left 3px + border-radius 100% + opacity 0 + transform scale(0) + transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) + + > .label + margin-left 28px + display block + font-size 16px + line-height 20px + cursor pointer + +.ui-radio[data-darkmode] + root(true) + +.ui-radio:not([data-darkmode]) + root(false) + +</style> diff --git a/src/client/app/common/views/components/ui/select.vue b/src/client/app/common/views/components/ui/select.vue new file mode 100644 index 0000000000..c56ae86159 --- /dev/null +++ b/src/client/app/common/views/components/ui/select.vue @@ -0,0 +1,212 @@ +<template> +<div class="ui-select" :class="[{ focused, filled }, styl]"> + <div class="icon" ref="icon"><slot name="icon"></slot></div> + <div class="input" @click="focus"> + <span class="label" ref="label"><slot name="label"></slot></span> + <div class="prefix" ref="prefix"><slot name="prefix"></slot></div> + <select ref="input" + :value="v" + :required="required" + @input="$emit('input', $event.target.value)" + @focus="focused = true" + @blur="focused = false"> + <slot></slot> + </select> + <div class="suffix"><slot name="suffix"></slot></div> + </div> + <div class="text"><slot name="text"></slot></div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: { + value: { + required: false + }, + required: { + type: Boolean, + required: false + } + }, + data() { + return { + v: this.value, + focused: false, + styl: 'fill' + }; + }, + computed: { + filled(): boolean { + return this.v != '' && this.v != null; + } + }, + watch: { + value(v) { + this.v = v; + } + }, + inject: ['isCardChild'], + created() { + if (this.isCardChild) { + this.styl = 'line'; + } + }, + mounted() { + if (this.$refs.prefix) { + this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px'; + } + }, + methods: { + focus() { + this.$refs.input.focus(); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +root(isDark, fill) + margin 32px 0 + + > .icon + position absolute + top 0 + left 0 + width 24px + text-align center + line-height 32px + color rgba(#000, 0.54) + + &:not(:empty) + .input + margin-left 28px + + > .input + display flex + + if fill + padding 6px 12px + background rgba(#000, 0.035) + border-radius 6px + else + &:before + content '' + display block + position absolute + bottom 0 + left 0 + right 0 + height 1px + background rgba(#000, 0.42) + + &:after + content '' + display block + position absolute + bottom 0 + left 0 + right 0 + height 2px + background $theme-color + opacity 0 + transform scaleX(0.12) + transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) + will-change border opacity transform + + > .label + position absolute + top fill ? 6px : 0 + left 0 + pointer-events none + transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1) + transition-duration 0.3s + font-size 16px + line-height 32px + color rgba(#000, 0.54) + pointer-events none + //will-change transform + transform-origin top left + transform scale(1) + + > select + display block + flex 1 + width 100% + padding 0 + font inherit + font-weight fill ? bold : normal + font-size 16px + height 32px + background transparent + border none + border-radius 0 + outline none + box-shadow none + + &[type='file'] + display none + + > .prefix + > .suffix + display block + align-self center + justify-self center + font-size 16px + line-height 32px + color rgba(#000, 0.54) + pointer-events none + + > * + display block + min-width 16px + + > .prefix + padding-right 4px + + > .suffix + padding-left 4px + + > .text + margin 6px 0 + font-size 13px + + * + margin 0 + + &.focused + > .input + if fill + background rgba(#000, 0.05) + else + &:after + opacity 1 + transform scaleX(1) + + > .label + color $theme-color + + &.focused + &.filled + > .input + > .label + top fill ? -24px : -16px + left 0 !important + transform scale(0.8) + +.ui-select[data-darkmode] + &.fill + root(true, true) + &:not(.fill) + root(true, false) + +.ui-select:not([data-darkmode]) + &.fill + root(false, true) + &:not(.fill) + root(false, false) + +</style> diff --git a/src/client/app/common/views/components/ui/switch.vue b/src/client/app/common/views/components/ui/switch.vue index f860309867..2431a76528 100644 --- a/src/client/app/common/views/components/ui/switch.vue +++ b/src/client/app/common/views/components/ui/switch.vue @@ -117,7 +117,7 @@ root(isDark) margin 3px 0 0 0 width 34px height 14px - background isDark ? rgba(#fff, 0.1) : rgba(#000, 0.05) + background isDark ? rgba(#fff, 0.1) : rgba(#000, 0.07) outline none border-radius 14px transition inherit diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index 25cfbf732b..beac788482 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -36,8 +36,8 @@ <div> <div class="md-body-2">%i18n:@post-style%</div> - <md-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</md-radio> - <md-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</md-radio> + <ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio> + <ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio> </div> </ui-card> @@ -68,17 +68,15 @@ <ui-card> <div slot="title">%fa:language% %i18n:@lang%</div> - <md-field> - <md-select v-model="lang" placeholder="%i18n:@auto%"> - <md-optgroup label="%i18n:@recommended%"> - <md-option value="">%i18n:@auto%</md-option> - </md-optgroup> + <ui-select v-model="lang" placeholder="%i18n:@auto%"> + <optgroup label="%i18n:@recommended%"> + <option value="">%i18n:@auto%</option> + </optgroup> - <md-optgroup label="%i18n:@specify-language%"> - <md-option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</md-option> - </md-optgroup> - </md-select> - </md-field> + <optgroup label="%i18n:@specify-language%"> + <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> + </optgroup> + </ui-select> <span class="md-helper-text">%fa:info-circle% %i18n:@lang-tip%</span> </ui-card>