Skip to content

Commit

Permalink
feat(tag): create tag component
Browse files Browse the repository at this point in the history
  • Loading branch information
AykutSarac committed Dec 17, 2024
1 parent f49c071 commit c76244c
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 52 deletions.
1 change: 1 addition & 0 deletions commitlint.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = {
"calendar",
"table",
"split-button",
"tag",
],
],
},
Expand Down
112 changes: 60 additions & 52 deletions scripts/build.js
Original file line number Diff line number Diff line change
@@ -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;
};

Expand All @@ -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}`);

Expand All @@ -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);
}

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/baklava.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
93 changes: 93 additions & 0 deletions src/components/tag/bl-tag.css
Original file line number Diff line number Diff line change
@@ -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);
}
103 changes: 103 additions & 0 deletions src/components/tag/bl-tag.ts
Original file line number Diff line number Diff line change
@@ -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`
<slot name="icon">
<bl-icon name=${this.icon}></bl-icon>
</slot>
`
: "";

const removeButton =
this.variant === "removeable"
? html`
<div role="button" type="button" class="remove-button">
<bl-icon name="close"></bl-icon>
</div>
`
: "";

return html`<button type="button" class="tag">
${icon}
<slot></slot>
${removeButton}
</button>`;
}
}

declare global {
interface HTMLElementTagNameMap {
"bl-tag": BlTag;
}
}

0 comments on commit c76244c

Please sign in to comment.