From 4db2cc50fd0b8ad62dd511cedce2717851a90bc3 Mon Sep 17 00:00:00 2001 From: "jiahui.xu" Date: Tue, 18 Feb 2025 15:29:03 +0800 Subject: [PATCH 1/3] fix: adjust build output path --- scripts/build.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/build.js b/scripts/build.js index 961e252..61220ad 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,4 +1,5 @@ import webpack from "webpack"; +import path from "path"; import fs from "node:fs"; import atImport from "postcss-import"; @@ -14,7 +15,7 @@ async function css() { // cssnano(), ]).process(content, { from: "src/main.css", to: "dist/main.css" }); - fs.writeFileSync("dist/all.css", output.css, "utf8"); + fs.writeFileSync(path.join(__dirname, "..", "dist", "all.css"), output.css, "utf8"); } async function js() { @@ -25,7 +26,7 @@ async function js() { // context: ".", resolve: { extensions: [".ts", ".css"] }, output: { - path: __dirname + "dist", + path: path.join(__dirname, "..", "dist"), filename: "index.js", library: "ui3", }, @@ -46,7 +47,7 @@ async function js() { } try { - fs.mkdirSync("dist"); + fs.mkdirSync(path.join(__dirname, "..", "dist")); } catch {} js(); -- Gitee From 98d542b67ab5ccec17053b76ac6a05b413d7bad6 Mon Sep 17 00:00:00 2001 From: "jiahui.xu" Date: Tue, 18 Feb 2025 15:30:18 +0800 Subject: [PATCH 2/3] feat: implement Button --- src/Button/index.css | 51 ------ src/Button/index.ts | 316 +++++++++++++++++++++++++++++++++++++- src/Icon/Loading/index.ts | 42 +++++ src/Icon/index.ts | 38 +++++ 4 files changed, 391 insertions(+), 56 deletions(-) delete mode 100644 src/Button/index.css create mode 100644 src/Icon/Loading/index.ts create mode 100644 src/Icon/index.ts diff --git a/src/Button/index.css b/src/Button/index.css deleted file mode 100644 index c7cf6a0..0000000 --- a/src/Button/index.css +++ /dev/null @@ -1,51 +0,0 @@ -ct-button { - font-family: var(--ct-font-family); - color: var(--ct-color-success); - font-size: var(--ct-font-size); - border-radius: var(--ct-border-radius); - cursor: pointer; - scrollbar-color: unset; - direction: initial; - margin: 0; - box-sizing: border-box; - outline: none; - position: relative; - display: inline-flex; - white-space: nowrap; - text-align: center; - user-select: none; - touch-action: manipulation; - padding: 8px; - background: rgb(22, 119, 255); - color: white; - box-shadow: rgba(5, 145, 255, 0.1) 0px 2px 0px 0px; - - &:focus:not([disabled]), - &:focus-visible { - outline: 2px solid #bde4ff; - } - - &:focus:not(:focus-visible) { - outline: 0; - } - - &:hover { - /* if you extend, please overwrite */ - background: #f4f6f7; - } - - &:active { - background: gray; - } - - &[hidden] { - display: none; - } - - &[disabled] { - pointer-events: none; - background: lightgray; - color: #adadad; - fill: #adadad; - } -} diff --git a/src/Button/index.ts b/src/Button/index.ts index c49e32f..4fcb366 100644 --- a/src/Button/index.ts +++ b/src/Button/index.ts @@ -1,12 +1,318 @@ -import { html, LitElement } from "lit"; +import { html, LitElement, css } from "lit"; import { property } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import "../Icon/Loading"; + +export type ButtonType = 'primary' | 'default' | 'dashed' | 'text' | 'link'; +export type ButtonSize = 'large' | 'middle' | 'small'; + +// 定义组件自身的属性 +const BUTTON_PROPERTIES = new Set([ + 'type', + 'size', + 'danger', + 'disabled', + 'loading', + 'icon', + 'href' +]); + +// 定义原生button允许的属性 +const NATIVE_PROPERTIES = new Set([ + 'type', + 'name', + 'form', + 'formAction', + 'formEnctype', + 'formMethod', + 'formNoValidate', + 'formTarget', + 'autofocus', + 'value', + 'tabindex', + 'title', + 'ariaLabel', + 'ariaDescribedby' +]); + +export class Button extends LitElement { + static styles = css` + :host { + display: inline-block; + } + + .ct-btn { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + padding: 4px 15px; + font-size: 13px; + border-radius: var(--ct-border-radius-small); + border: 1px solid var(--ct-color-border); + cursor: pointer; + transition: all 0.2s; + user-select: none; + text-decoration: none; + } + + /* Types */ + .ct-btn--primary { + color: white; + background: var(--ct-color-primary); + border-color: var(--ct-color-primary); + } + + .ct-btn--primary:hover { + background: var(--ct-color-primary-hover); + border-color: var(--ct-color-primary-hover); + } + + .ct-btn--primary:active { + background: var(--ct-color-primary-active); + border-color: var(--ct-color-primary-active); + } + + .ct-btn--default { + background: var(--ct-color-fill-0); + border-color: var(--ct-color-border); + color: var(--ct-color-text-0); + } + + .ct-btn--default:hover { + color: var(--ct-color-primary); + border-color: var(--ct-color-primary); + } + + .ct-btn--dashed { + background: transparent; + border: 1px dashed var(--ct-color-border); + color: var(--ct-color-text-0); + } + + .ct-btn--dashed:hover { + color: var(--ct-color-primary); + border-color: var(--ct-color-primary); + } + + .ct-btn--text { + background: transparent; + border-color: transparent; + color: var(--ct-color-primary); + } + + .ct-btn--text:hover { + background: var(--ct-color-light-hover); + } + + .ct-btn--text:active { + background: var(--ct-color-light-active); + } + + .ct-btn--link { + background: transparent; + border-color: transparent; + color: var(--ct-color-primary); + padding: 0; + } + + .ct-btn--link:hover { + color: var(--ct-color-primary-hover); + } + + /* Sizes */ + .ct-btn--large { + padding: 6px 20px; + font-size: 16px; + } + + .ct-btn--small { + padding: 2px 12px; + font-size: 12px; + } + + /* States */ + .ct-btn--danger { + color: var(--ct-color-danger); + border-color: var(--ct-color-danger); + } + + .ct-btn--danger:hover { + color: var(--ct-color-danger-hover); + border-color: var(--ct-color-danger-hover); + } + + .ct-btn--danger.ct-btn--primary { + background: var(--ct-color-danger); + border-color: var(--ct-color-danger); + color: white; + } + + .ct-btn--danger.ct-btn--primary:hover { + background: var(--ct-color-danger-hover); + border-color: var(--ct-color-danger-hover); + } + + .ct-btn:disabled { + cursor: not-allowed; + opacity: 0.65; + } + + /* Loading */ + .ct-btn--loading { + position: relative; + cursor: not-allowed; + } + + .ct-btn__icon { + display: inline-flex; + align-items: center; + margin-right: 8px; + } + + .ct-btn__loading-icon { + display: inline-flex; + margin-right: 8px; + } + + .ct-btn--small .ct-btn__loading-icon { + width: 12px; + height: 12px; + } + + .ct-btn--large .ct-btn__loading-icon { + width: 16px; + height: 16px; + } + + /* Active state */ + :host(:active), + :host([active]) { + transform: scale(0.98); + } + + .ct-btn:active, + .ct-btn[active] { + transform: scale(0.98); + } + `; + + // 组件自身属性 + @property({ type: String, reflect: true }) + type: ButtonType = 'default'; + + @property({ type: String, reflect: true }) + size: ButtonSize = 'middle'; + + @property({ type: Boolean, reflect: true }) + danger = false; + + @property({ type: Boolean, reflect: true }) + disabled = false; + + @property({ type: Boolean }) + loading = false; + + @property() + icon?: string; -class Button extends LitElement { @property() - type?: "link"; + href?: string; + + @property({ type: Boolean, reflect: true }) + active = false; + + // 收集所有原生属性 + private get buttonProps() { + const props: Record = {}; + + // 获取所有属性 + const attrs = this.attributes; + for (let i = 0; i < attrs.length; i++) { + const attr = attrs[i]; + const propName = attr.name; + + // 跳过组件自身的属性 + if (BUTTON_PROPERTIES.has(propName)) { + continue; + } + + // 只收集原生button允许的属性 + if (NATIVE_PROPERTIES.has(propName)) { + props[propName] = attr.value; + } + } + + return props; + } + + private renderLoadingIcon() { + return html` + + + + `; + } + + // 更新 handleClick 方法 + private handleClick(e: MouseEvent) { + // 阻止事件冒泡到 ct-button 元素 + e.stopPropagation(); + + if (this.disabled || this.loading) { + e.preventDefault(); + return; + } + + // 分发点击事件 + this.dispatchEvent(new CustomEvent('click', { + bubbles: true, + composed: true, + cancelable: true, + detail: { originalEvent: e } + })); + } render() { - return html``; + const classes = { + 'ct-btn': true, + [`ct-btn--${this.type}`]: true, + [`ct-btn--${this.size}`]: this.size !== 'middle', + 'ct-btn--danger': this.danger, + }; + + const content = html` + ${this.loading ? this.renderLoadingIcon() : null} + ${!this.loading && this.icon ? html`${this.icon}` : null} + + `; + + if (this.href && this.type === 'link') { + return html` + + ${content} + + `; + } + + return html` + + `; } connectedCallback() { @@ -15,4 +321,4 @@ class Button extends LitElement { } } -export default Button; +export default Button; \ No newline at end of file diff --git a/src/Icon/Loading/index.ts b/src/Icon/Loading/index.ts new file mode 100644 index 0000000..b0f18f4 --- /dev/null +++ b/src/Icon/Loading/index.ts @@ -0,0 +1,42 @@ +import { html, css, unsafeCSS } from "lit"; +import { Icon } from "../index"; + +export class IconLoading extends Icon { + static styles = css` + ${unsafeCSS(Icon.styles)} + @keyframes rotate { + 100% { + transform: rotate(360deg); + } + } + + :host .ct-icon { + animation: rotate 1s linear infinite; + width: inherit; + height: inherit; + } + `; + + constructor() { + super(); + this.type = 'loading'; + } + + render() { + return html` + + + + `; + } +} + +customElements.define('ct-icon-loading', IconLoading); + +export default IconLoading; \ No newline at end of file diff --git a/src/Icon/index.ts b/src/Icon/index.ts new file mode 100644 index 0000000..8479971 --- /dev/null +++ b/src/Icon/index.ts @@ -0,0 +1,38 @@ +import { html, LitElement, css } from "lit"; +import { property } from "lit/decorators.js"; + +export class Icon extends LitElement { + static styles = css` + :host { + display: inline-flex; + } + + /* 默认尺寸 14px */ + :host([size="middle"]) .ct-icon { + width: 14px; + height: 14px; + } + + /* 小尺寸 12px */ + :host([size="small"]) .ct-icon { + width: 12px; + height: 12px; + } + + /* 大尺寸 16px */ + :host([size="large"]) .ct-icon { + width: 16px; + height: 16px; + } + `; + + @property({ type: String, reflect: true }) + size: 'small' | 'middle' | 'large' = 'middle'; + + @property({ type: String }) + type: string = ''; +} + +customElements.define('ct-icon', Icon); + +export default Icon; \ No newline at end of file -- Gitee From 7e3bd4fab932f318aebffc8ef744b0534c61be8a Mon Sep 17 00:00:00 2001 From: "jiahui.xu" Date: Tue, 18 Feb 2025 15:33:05 +0800 Subject: [PATCH 3/3] test: update ct-button examples in index.html --- index.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index a541d09..b1c086f 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,20 @@
- Button +

+ Primary Button + Default Button + Dashed Button + Text Button + Link Button + Large Button + Small Button + Loading Button + Disabled Button + Danger Button +

+ +

-- Gitee