diff --git a/Readme.md b/Readme.md index 93faafc..b9086cf 100644 --- a/Readme.md +++ b/Readme.md @@ -22,7 +22,7 @@ - [ ] `wc-badge`徽标组件 - [ ] `wc-counter`倒计时组件 - [ ] `wc-drag`拖拽组件 -- [ ] `wc-button`表单组件-按钮 +- [x] `wc-button`表单组件-按钮 - [ ] `wc-checkbox`表单组件-复选框 - [ ] `wc-input`表单组件-文本输入框 - [ ] `wc-number`表单组件-步进数字输入 diff --git a/develop.md b/develop.md index 4f0c8c7..28ad8b2 100644 --- a/develop.md +++ b/develop.md @@ -18,7 +18,7 @@ - + diff --git a/src/form/button.js b/src/form/button.js index a43abf2..ec63169 100644 --- a/src/form/button.js +++ b/src/form/button.js @@ -4,207 +4,267 @@ * @date 2023/03/06 15:17:25 */ -import { css, html, Component } from '@bd/core' +import { css, html, Component, $, nextTick } from '@bd/core' import '../icon/index.js' class Button extends Component { static props = { + type: 'primary', icon: '', - autofocus: '', - loading: false, + size: 'l', + autofocus: false, + loading: { + type: Boolean, + default: false, + observer(val) { + if (val) { + this.cacheIcon = this.icon + this.icon = 'loading' + } else { + this.icon = this.cacheIcon || '' + } + } + }, disabled: false, lazy: 0 // 并发拦截时间, 单位毫秒 } - static styles = css` - :host { - overflow: hidden; - display: inline-flex; - min-width: 128px; - height: 36px; - border-radius: 3px; - user-select: none; - -moz-user-select: none; - color: var(--color-dark-1); - font-size: 14px; - cursor: pointer; - transition: box-shadow 0.15s linear; + static styles = [ + // 基础样式 + css` + :host { + overflow: hidden; + display: inline-flex; + border: 0; + border-radius: 3px; + user-select: none; + -moz-user-select: none; + color: var(--color-dark-1); + font-size: 14px; + cursor: pointer; + transition: box-shadow 0.15s linear; - button { - display: flex; - justify-content: space-evenly; - align-items: center; - width: 100%; - height: inherit; - padding: var(--padding, 0 14px); - line-height: 1; - border: 1px solid var(--color-grey-2); - border-radius: inherit; - white-space: nowrap; - background: #fff; - font-size: inherit; - font-family: inherit; - outline: none; - color: inherit; - cursor: inherit; - transition: background 0.15s linear; - - &::-moz-focus-inner { - border: none; - } - } - - .icon { - --size: var(--icon-size, 18px); - } - } - - :host([size='large']) { - min-width: 212px; - height: 52px; - font-size: 18px; - - button { - padding: 0 24px; - } - - .icon { - --size: 26px; - } - } - :host([size='large'][circle]) { - min-width: 52px; - width: 52px; - height: 52px; - - button { - padding: 0; - } - } - :host([size='medium']) { - min-width: 160px; - height: 44px; - - button { - padding: 0 18px; - } - } - :host([size='medium'][circle]) { - min-width: 44px; - width: 44px; - } - :host([size='small']) { - min-width: 96px; - height: 32px; - } - :host([size='small'][circle]) { - min-width: 32px; - width: 32px; - } - :host([size='mini']) { - min-width: 72px; - height: 26px; - font-size: 12px; - - button { - padding: 0 6px; - } - - .icon { - --size: 14px; - } - } - :host([size='mini'][circle]) { - min-width: 26px; - width: 26px; - } - - :host([round]) { - border-radius: 26px; - } - :host([circle]) { - min-width: 36px; - width: 36px; - border-radius: 50%; - button { - padding: 0; - } - .icon { - margin-right: 0; - } - - slot { - display: none; - } - } - - :host(:focus-within) { - box-shadow: 0 0 0 2px var(--color-plain-a); - } - - $colors: ( - primary: 'teal', - info: 'blue', - success: 'green', - warning: 'orange', - danger: 'red', - secondary: 'dark', - help: 'grey' - ); - - @loop $t, $c in $colors { - :host([type='#{$t}']) { button { - color: var(--color-#{$c}-2); - border: 1px solid var(--color-#{$c}-2); + display: flex; + justify-content: center; + align-items: center; + width: 100%; + min-width: 1px; + height: inherit; + padding: var(--button-padding, 0 4px); + line-height: 1; + border: 1px solid transparent; + border-radius: inherit; + white-space: nowrap; + background: #fff; + font-size: inherit; + font-family: inherit; + outline: none; + color: inherit; + cursor: inherit; + transition: background 0.15s linear; - &:hover { - color: var(--color-#{$c}-1); - border-color: var(--color-#{$c}-1); - } - &:active { - color: var(--color-#{$c}-3); - } - &:disabled { - color: var(--color-#{$c}-2); + &::-moz-focus-inner { + border: none; } } - &:host([solid]) button { - border: 0; - color: #fff; - background: var(--color-#{$c}-2); - - &:hover { - background: var(--color-#{$c}-1); - } - &:active { - background: var(--color-#{$c}-3); - } - - &:disabled { - background: var(--color-#{$c}-2); - } - } - - &:host(:focus-within) { - box-shadow: 0 0 0 2px var(--color-#{$c}-a); + .icon { + --size: var(--icon-size, 18px); + margin-right: 4px; } } - } + :host(:focus-within) { + box-shadow: 0 0 0 2px var(--color-plain-a); + } + `, + // 尺寸 + css` + @use 'sass:map'; + $sizes: ( + s: ( + w: 52px, + h: 20px, + f: 12px + ), + m: ( + w: 72px, + h: 24px, + f: 12px + ), + l: ( + w: 108px, + h: 32px, + f: 14px + ), + xl: ( + w: 132px, + h: 36px, + f: 14px + ), + xxl: ( + w: 160px, + h: 44px, + f: 14px + ), + xxxl: ( + w: 192px, + h: 52px, + f: 16px + ), + xxxxl: ( + w: 212px, + h: 64px, + f: 18px + ) + ); - :host([loading]), - :host([disabled]) { - cursor: not-allowed; - opacity: 0.6; + @loop $s, $v in $sizes { + :host([size='#{$s}']) { + min-width: map.get($v, 'w'); + height: map.get($v, 'h'); + font-size: map.get($v, 'f'); + + .icon { + --size: #{map.get($v, 'f')}; + } + } + :host([size='#{$s}'][circle]) { + width: map.get($v, 'h'); + height: map.get($v, 'h'); + + button { + } + } + } + + :host([dashed]) button { + border-style: dashed; + } + + :host([round]) { + border-radius: 32px; + } + :host([circle]) { + min-width: 0; + border-radius: 50%; + button { + padding: 0; + } + .icon { + margin-right: 0; + } + + slot { + display: none; + } + } + `, + // 配色 + css` + $colors: ( + primary: 'teal', + info: 'blue', + success: 'green', + warning: 'orange', + danger: 'red', + secondary: 'dark', + help: 'grey' + ); + + @loop $t, $c in $colors { + :host([type='#{$t}']) { + button { + color: var(--color-#{$c}-2); + border-color: var(--color-#{$c}-2); + + &:hover { + color: var(--color-#{$c}-1); + border-color: var(--color-#{$c}-1); + } + &:active { + color: var(--color-#{$c}-3); + } + &:disabled { + color: var(--color-#{$c}-2); + } + } + + &:host([solid]) button { + border: 0; + color: #fff; + background: var(--color-#{$c}-2); + + &:hover { + background: var(--color-#{$c}-1); + } + &:active { + background: var(--color-#{$c}-3); + } + + &:disabled { + background: var(--color-#{$c}-2); + } + } + + &:host(:focus-within) { + box-shadow: 0 0 0 2px var(--color-#{$c}-a); + } + } + } + `, + // 状态 + css` + :host([loading]), + :host([disabled]) { + cursor: not-allowed; + opacity: 0.6; + } + ` + ] + + created() { + this.stamp = 0 + + this._clickFn = this.$on( + 'click', + ev => { + let { loading, disabled, lazy } = this + let now = Date.now() + + if (loading || disabled) { + return ev.stopPropagation() + } + // 并发拦截 + if (lazy > 0 && now - this.stamp < lazy) { + return ev.stopPropagation() + } + this.stamp = now + }, + true + ) + } + + mounted() { + if (this.autofocus) { + let $btn = $('button', this.root) + $btn.setAttribute('autofocus', '') + // 需要focus()才能聚焦成功 + nextTick(_ => $btn.focus()) } - ` + } + + unmounted() { + this.$off('click', this._clickFn) + } render() { - return html`` + return html` + + ` } } diff --git a/src/form/link.js b/src/form/link.js new file mode 100644 index 0000000..3e25971 --- /dev/null +++ b/src/form/link.js @@ -0,0 +1,156 @@ +/** + * {} + * @author yutent + * @date 2023/03/16 17:40:50 + */ + +import { css, html, Component, bind, unbind, $, nextTick } from '@bd/core' +class Link extends Component { + static props = { + type: 'primary', + to: '', + autofocus: false, + disabled: false, + lazy: 0 // 并发拦截时间, 单位毫秒 + } + + static styles = [ + // 基础样式 + css` + :host { + position: relative; + display: inline-flex; + border-radius: 2px; + user-select: none; + -moz-user-select: none; + font-size: inherit; + cursor: pointer; + transition: box-shadow 0.15s linear; + + .link { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: var(--padding, 0 2px); + line-height: 1; + font-size: inherit; + font-family: inherit; + outline: none; + color: inherit; + cursor: inherit; + text-decoration: none; + transition: color 0.15s linear; + + &::-moz-focus-inner { + border: none; + } + } + + &::after { + position: absolute; + bottom: -2px; + left: 0; + width: 100%; + height: 1px; + border-bottom: 1px dashed transparent; + content: ''; + opacity: 0; + transition: opacity 0.15s linear; + } + } + `, + // 配色 + css` + $colors: ( + primary: 'teal', + info: 'blue', + success: 'green', + warning: 'orange', + danger: 'red', + secondary: 'dark', + help: 'grey' + ); + + @loop $t, $c in $colors { + :host([type='#{$t}']) { + color: var(--color-#{$c}-2); + &::after { + border-color: var(--color-#{$c}-1); + } + } + :host([type='#{$t}']:not([disabled]):hover) { + color: var(--color-#{$c}-1); + } + :host([type='#{$t}']:not([disabled]):active) { + color: var(--color-#{$c}-3); + } + :host([type='#{$t}']:not([disabled]):focus-within) { + box-shadow: 0 0 0 2px var(--color-#{$c}-a); + } + } + `, + // 状态 + css` + :host(:not([disabled]):hover), + :host([underline]) { + &::after { + opacity: 1; + } + } + :host([disabled]) { + cursor: not-allowed; + opacity: 0.6; + } + ` + ] + + mounted() { + let $a = $('.link', this.root) + this.stamp = 0 + + if (this.autofocus) { + $a.setAttribute('autofocus', '') + // 需要focus()才能聚焦成功 + nextTick(_ => $a.focus()) + } + + this._clickFn = bind( + $a, + 'click', + ev => { + let { disabled, lazy } = this + let now = Date.now() + + // 除了事件冒泡之外, a标签的默认事件也要阻止 + if (disabled) { + ev.preventDefault() + ev.stopPropagation() + return + } + // 并发拦截 + if (lazy > 0 && now - this.stamp < lazy) { + ev.preventDefault() + ev.stopPropagation() + return + } + this.stamp = now + }, + true + ) + } + + unmounted() { + unbind($('.link', this.root), 'click', this._clickFn) + } + + render() { + return html` + + + + ` + } +} + +customElements.define('wc-link', Link) diff --git a/开发规范.md b/开发规范.md index 34e61d9..963c1a2 100644 --- a/开发规范.md +++ b/开发规范.md @@ -4,7 +4,7 @@ > 主题功能, 暂时由根样式定义来实现, 所以, 组件内的样式, 尽可能避免过多的私有配色。 2. 组件几种配色样式, 暂时同大多数组件一致 - - `type=primary` 青色 + - `type=primary` 青色 (默认值) - `type=secondary` 暗色 - `type=info` 蓝色 - `type=success` 绿色