From c76244cc7843e503e61a7c90d2ab16ddc96a0231 Mon Sep 17 00:00:00 2001 From: AykutSarac Date: Tue, 17 Dec 2024 17:40:05 +0300 Subject: [PATCH] feat(tag): create tag component --- commitlint.config.cjs | 1 + scripts/build.js | 112 ++++++++++++++++++---------------- src/baklava.ts | 1 + src/components/tag/bl-tag.css | 93 ++++++++++++++++++++++++++++ src/components/tag/bl-tag.ts | 103 +++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 52 deletions(-) create mode 100644 src/components/tag/bl-tag.css create mode 100644 src/components/tag/bl-tag.ts diff --git a/commitlint.config.cjs b/commitlint.config.cjs index 515a25f7..3c2eafa4 100644 --- a/commitlint.config.cjs +++ b/commitlint.config.cjs @@ -36,6 +36,7 @@ module.exports = { "calendar", "table", "split-button", + "tag", ], ], }, diff --git a/scripts/build.js b/scripts/build.js index bd13ba52..ca3cf8f4 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,34 +1,46 @@ -import { context, build } from 'esbuild'; -import parseArgs from 'minimist'; -import CleanCSS from 'clean-css'; -import del from 'del'; -import { litCssPlugin } from 'esbuild-plugin-lit-css'; +import del from "del"; +import { context, build } from "esbuild"; +import parseArgs from "minimist"; +import CleanCSS from "clean-css"; +import { litCssPlugin } from "esbuild-plugin-lit-css"; const args = parseArgs(process.argv.slice(2), { boolean: true, }); (async () => { - const { globby } = await import('globby'); - const destinationPath = 'dist'; + const { globby } = await import("globby"); + const destinationPath = "dist"; const isRelease = process.env.RELEASE || false; /* This is for using inside Storybook for demonstration purposes. */ - const cssHoverClassAdder = (content) => content.replace(/.*:hover[^{]*/g, matched => { - // Replace :hover with special class. (There will be additional classes for focus, etc. Should be implemented in here.) - const replacedWithNewClass = matched.replace(/:hover/, '.__ONLY_FOR_STORYBOOK_DEMONSTRATION_HOVER__') - // Concat strings - return replacedWithNewClass.concat(', ', matched); - }); - - const cssCleaner = (content) => { - const { styles, errors, warnings } = new CleanCSS({ level: 0 }).minify(content); + const cssHoverClassAdder = content => + content.replace(/.*:hover[^{]*/g, matched => { + // Replace :hover with special class. (There will be additional classes for focus, etc. Should be implemented in here.) + const replacedWithNewClass = matched.replace( + /:hover/, + ".__ONLY_FOR_STORYBOOK_DEMONSTRATION_HOVER__" + ); + // Concat strings + return replacedWithNewClass.concat(", ", matched); + }); + + const cssCleaner = content => { + const { styles, errors, warnings } = new CleanCSS({ level: 2 }).minify(content); if (errors.length) { - console.error(errors); + console.error({ + errors, + styles: JSON.stringify(styles), + }); } + if (warnings.length) { - console.warn(warnings); + console.warn({ + warnings, + styles: JSON.stringify(styles), + }); } + return styles; }; @@ -43,57 +55,53 @@ const args = parseArgs(process.argv.slice(2), { const cssPluginOptions = { filter: /components\/.*\.css$/, - transform: (content) => cssTransformers.reduce((result, transformer) => transformer(result), content) + transform: content => + cssTransformers.reduce((result, transformer) => transformer(result), content), }; try { const buildOptions = { entryPoints: [ - 'src/baklava.ts', - 'src/baklava-react.ts', - 'src/localization.ts', + "src/baklava.ts", + "src/baklava-react.ts", + "src/localization.ts", ...(await globby([ - 'src/generated/**/*.ts', - 'src/components/**/!(*.(test|d)).ts', - 'src/themes/*.css', - 'src/components/**/*.svg', + "src/generated/**/*.ts", + "src/components/**/!(*.(test|d)).ts", + "src/themes/*.css", + "src/components/**/*.svg", ])), ], loader: { - '.woff': 'file', - '.woff2': 'file', - '.svg': 'file', + ".woff": "file", + ".woff2": "file", + ".svg": "file", }, outdir: destinationPath, - assetNames: 'assets/[name]', + assetNames: "assets/[name]", bundle: true, sourcemap: true, - format: 'esm', - target: ['es2020', 'chrome73', 'edge79', 'firefox63', 'safari12'], + format: "esm", + target: ["es2020", "chrome73", "edge79", "firefox63", "safari12"], splitting: true, metafile: true, minify: true, - external: ['react'], - plugins: [ - litCssPlugin(cssPluginOptions), - ], + external: ["react"], + plugins: [litCssPlugin(cssPluginOptions)], }; - if (args.serve) { - const servedir = 'playground'; + const servedir = "playground"; let ctx = await context({ ...buildOptions, - outdir: `${servedir}/dist` + outdir: `${servedir}/dist`, }); - const { host, port } = await ctx.serve( - { - servedir, - host: 'localhost', - } - ); + const { host, port } = await ctx.serve({ + servedir, + host: "localhost", + }); console.log(`Playground is served on http://${host}:${port}`); @@ -104,12 +112,12 @@ const args = parseArgs(process.argv.slice(2), { if (errors.length > 0) { console.table(errors); - console.error('Build Failed!'); + console.error("Build Failed!"); return; } if (warnings.length > 0) { - console.warn('Warnings:'); + console.warn("Warnings:"); console.table(warnings); } @@ -122,19 +130,19 @@ const args = parseArgs(process.argv.slice(2), { .filter( ({ fileName }) => !/icon\/icons\/.*\.js/.test(fileName) && - (fileName.endsWith('.js') || fileName.endsWith('.css')) + (fileName.endsWith(".js") || fileName.endsWith(".css")) ); analyzeResult.push({ - fileName: 'TOTAL', + fileName: "TOTAL", size: `${(analyzeResult.reduce((acc, { bytes }) => acc + bytes, 0) / 1024).toFixed(2)} KB`, - }) + }); del(`${destinationPath}/components/icon/icons`); - console.table(analyzeResult, ['fileName', 'size']); + console.table(analyzeResult, ["fileName", "size"]); - console.info('Build Done!'); + console.info("Build Done!"); } catch (error) { console.error(error); process.exit(1); diff --git a/src/baklava.ts b/src/baklava.ts index 72f1a5f8..696a10c8 100644 --- a/src/baklava.ts +++ b/src/baklava.ts @@ -36,4 +36,5 @@ export { default as BlTableHeaderCell } from "./components/table/table-header-ce export { default as BlTableCell } from "./components/table/table-cell/bl-table-cell"; export { default as BlSplitButton } from "./components/split-button/bl-split-button"; export { default as BlCalendar } from "./components/calendar/bl-calendar"; +export { default as BlTag } from "./components/tag/bl-tag"; export { getIconPath, setIconPath } from "./utilities/asset-paths"; diff --git a/src/components/tag/bl-tag.css b/src/components/tag/bl-tag.css new file mode 100644 index 00000000..c02fd86c --- /dev/null +++ b/src/components/tag/bl-tag.css @@ -0,0 +1,93 @@ +:host { + display: inline-block; + max-width: 100%; +} + +.tag { + --bg-color: var(--bl-color-neutral-full); + --color: var(--bl-color-neutral-darker); + --font: var(--bl-font-title-4-medium); + --padding-vertical: var(--bl-size-2xs); + --padding-horizontal: var(--bl-size-m); + --margin-icon: var(--bl-size-2xs); + --icon-size: var(--bl-size-m); + --height: var(--bl-size-2xl); + --border-radius: var(--bl-size-m); + + display: flex; + gap: var(--margin-icon); + justify-content: center; + align-items: center; + box-sizing: border-box; + width: 100%; + border: 1px solid var(--bl-color-neutral-lighter); + border-radius: var(--border-radius); + padding: var(--padding-vertical) var(--padding-horizontal); + background-color: var(--bg-color); + color: var(--color, white); + font: var(--font); + font-kerning: none; + height: var(--height); +} + +:host([variant="selectable"]) .tag { + cursor: pointer; +} + +:host([variant="selectable"]) .tag:hover { + background-color: var(--bl-color-neutral-lightest); +} + +:host([variant="selectable"][selected]) .tag { + border-color: var(--bl-color-neutral-darker); + background-color: var(--bl-color-neutral-darker); + color: var(--bl-color-neutral-full); +} + +:host([disabled]) { + pointer-events: none; +} + +:host([disabled]) .tag { + background-color: var(--bl-color-neutral-lightest); + border-color: var(--bl-color-neutral-lightest); + color: var(--bl-color-neutral-light); +} + +.remove-button { + all: unset; + font-size: var(--bl-size-s); + padding: var(--bl-size-3xs); + border-radius: var(--bl-border-radius-circle); + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.remove-button:hover { + background-color: var(--bl-color-neutral-lightest); +} + +:host([size="small"]) .tag { + --font: var(--bl-font-title-4-medium); + --height: var(--bl-size-xl); + --icon-size: var(--bl-size-s); + --border-radius: var(--bl-size-xs); + --padding-vertical: var(--bl-size-3xs); + --padding-horizontal: var(--bl-size-2xs); +} + +:host([size="large"]) .tag { + --font: var(--bl-font-title-3-medium); + --padding-vertical: var(--bl-size-2xs); + --padding-horizontal: var(--bl-size-l); + --height: var(--bl-size-3xl); + --icon-size: var(--bl-size-m); + --border-radius: var(--bl-size-l); +} + +/* TODO: çalışmıyor */ +:host ::slotted(bl-icon) { + font-size: var(--icon-size); +} diff --git a/src/components/tag/bl-tag.ts b/src/components/tag/bl-tag.ts new file mode 100644 index 00000000..8e3a92d0 --- /dev/null +++ b/src/components/tag/bl-tag.ts @@ -0,0 +1,103 @@ +import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators.js"; +import { event, EventDispatcher } from "../../utilities/event"; +import "../icon/bl-icon"; +import { BaklavaIcon } from "../icon/icon-list"; +import style from "./bl-tag.css"; + +export type TagSize = "small" | "medium" | "large"; +type TagVariant = "selectable" | "removeable"; + +/** + * @tag bl-tag + * @summary Baklava Tag component + */ + +@customElement("bl-tag") +export default class BlTag extends LitElement { + static get styles(): CSSResultGroup { + return [style]; + } + + @state() private _actionElement: HTMLElement | null = null; + @query(".remove-button") removeButton!: HTMLButtonElement; + + /** + * Sets the tag size + */ + @property({ type: String, reflect: true }) + size: TagSize = "medium"; + + @property({ type: String, reflect: true }) + variant: TagVariant = "selectable"; + + @property({ type: Boolean, reflect: true }) + selected = false; + + @property({ type: Boolean, reflect: true }) + disabled = false; + + @property({ type: String, reflect: true }) + value: string | null = null; + + @event("bl-tag-click") _onBlTagClick: EventDispatcher<{ + value: string | null; + selected: boolean; + }>; + + connectedCallback(): void { + super.connectedCallback(); + + this._actionElement?.addEventListener("click", this.handleClick); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + + this._actionElement?.removeEventListener("click", this.handleClick); + } + + private handleClick() { + console.log(this._actionElement); + + if (this.variant === "selectable") this.selected = !this.selected; + this._onBlTagClick({ selected: this.selected, value: this.value }); + } + + /** + * Sets the name of the icon + */ + @property({ type: String }) + icon?: BaklavaIcon; + + render(): TemplateResult { + const icon = this.icon + ? html` + + + + ` + : ""; + + const removeButton = + this.variant === "removeable" + ? html` +
+ +
+ ` + : ""; + + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + "bl-tag": BlTag; + } +}