Skip to content

Commit

Permalink
feat(Button): extracting Button and peers as a standalone package (#635)
Browse files Browse the repository at this point in the history
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new `ui-button` package, expanding the UI component
library with button functionalities.
	- Added Tailwind CSS integration for improved styling capabilities.

- **Bug Fixes**
- Updated dependencies to ensure compatibility and performance
improvements.

- **Documentation**
- Added README for the new `ui-button` package, detailing its purpose
and usage.

- **Refactor**
- Updated import paths for button components across the UI component
library to streamline access.

- **Tests**
- Enhanced test coverage for the `ButtonIcon` component to ensure proper
rendering with specific props.
- Improved error handling in the testing environment to catch critical
React warnings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
aversini authored Sep 15, 2024
1 parent c090c1a commit 651f857
Show file tree
Hide file tree
Showing 39 changed files with 386 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"packages/ui-icons": "1.12.0",
"packages/ui-styles": "1.9.6",
"packages/ui-form": "1.3.10",
"packages/ui-fingerprint": "1.0.1"
"packages/ui-fingerprint": "1.0.1",
"packages/ui-button": "0.0.0"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"@versini/dev-dependencies-client": "6.0.2",
"@versini/dev-dependencies-types": "1.3.5"
},
"packageManager": "pnpm@9.9.0+sha512.60c18acd138bff695d339be6ad13f7e936eea6745660d4cc4a776d5247c540d0edee1a563695c183a66eb917ef88f2b4feb1fc25f32a7adcadc7aaf3438e99c1"
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c"
}
3 changes: 3 additions & 0 deletions packages/ui-button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @versini/ui-button

This is a button component.
49 changes: 49 additions & 0 deletions packages/ui-button/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@versini/ui-button",
"version": "0.0.0",
"license": "MIT",
"author": "Arno Versini",
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/aversini/ui-components",
"repository": {
"type": "git",
"url": "[email protected]:aversini/ui-components.git"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build:check": "tsc",
"build:js": "vite build",
"build:types": "tsup",
"build": "npm-run-all --serial clean build:check build:js build:types",
"clean": "rimraf dist tmp",
"dev:js": "vite build --watch --mode development",
"dev:types": "tsup --watch src",
"dev": "npm-run-all clean --parallel dev:js dev:types",
"lint": "biome lint src",
"start": "static-server dist --port 5173",
"test:coverage:ui": "vitest --coverage --ui",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
"test": "vitest run"
},
"peerDependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@versini/ui-icons": "workspace:../ui-icons",
"@versini/ui-styles": "workspace:../ui-styles"
},
"dependencies": {
"@tailwindcss/typography": "0.5.15",
"@versini/ui-private": "workspace:../ui-private",
"clsx": "2.1.1",
"tailwindcss": "3.4.11"
},
"sideEffects": ["**/*.css"]
}
6 changes: 6 additions & 0 deletions packages/ui-button/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
1 change: 1 addition & 0 deletions packages/ui-button/src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BUTTON_CLASSNAME = "av-button";
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ describe("ButtonIcon modifiers", () => {
expect(button.className).toContain("w-full");
});

it("should render a button icon with no background", async () => {
render(
<ButtonIcon noBackground>
<IconSettings />
</ButtonIcon>,
);
const button = await screen.findByRole("button");
expect(button.className).toContain("not-prose");
expect(button.className).toContain("rounded-full");
});

it("should render a left-aligned button icon", async () => {
render(
<ButtonIcon align="left">
Expand Down
6 changes: 6 additions & 0 deletions packages/ui-button/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Button } from "./Button/Button";
import { ButtonIcon } from "./Button/ButtonIcon";
import { ButtonLink } from "./Button/ButtonLink";
import { ButtonSort as ButtonSort_private } from "./private/ButtonSort";

export { Button, ButtonIcon, ButtonLink, ButtonSort_private };
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { ButtonProps } from "../Button/ButtonTypes";
* old cranky Safari.
*
*/
/* c8 ignore start */
const internalClick = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
noInternalClick: boolean,
Expand All @@ -30,6 +31,7 @@ const internalClick = (

typeof onClick === "function" && onClick(e);
};
/* c8 ignore end */

export const BaseButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
(buttonProps: any, ref) => {
Expand Down
3 changes: 3 additions & 0 deletions packages/ui-button/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
1 change: 1 addition & 0 deletions packages/ui-button/src/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./index.css";
1 change: 1 addition & 0 deletions packages/ui-button/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
7 changes: 7 additions & 0 deletions packages/ui-button/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('tailwindcss').Config} */

import { twPlugin } from "@versini/ui-styles";

export default twPlugin.merge({
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
});
26 changes: 26 additions & 0 deletions packages/ui-button/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"types": ["vitest/globals", "@testing-library/jest-dom"],

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
10 changes: 10 additions & 0 deletions packages/ui-button/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["./vite.config.ts", "./vitest.setup.ts"]
}
12 changes: 12 additions & 0 deletions packages/ui-button/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from "tsup";

export default defineConfig({
format: "esm",
entry: {
index: "src/components/index.ts",
},
outDir: "dist",
dts: {
only: true,
},
});
113 changes: 113 additions & 0 deletions packages/ui-button/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import path from "node:path";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

import fs from "fs-extra";
import { glob } from "glob";
import { defineConfig } from "vite";

import { externalDependencies } from "../../configuration/vite.common";

const packageJson = fs.readJSONSync("package.json");
const copyrightYear = new Date(Date.now()).getFullYear();
const buildTime = new Date()
.toLocaleString("en-US", {
timeZone: "America/New_York",
timeZoneName: "short",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})
.replace(/,/g, "");
const banner = `/*!
${packageJson.name} v${packageJson.version}
© ${copyrightYear} gizmette.com
*/
try {
if (!window.__VERSINI_UI_BUTTON__) {
window.__VERSINI_UI_BUTTON__ = {
version: "${packageJson.version}",
buildTime: "${buildTime}",
homepage: "${packageJson.homepage}",
license: "${packageJson.license}",
};
}
} catch (error) {
// nothing to declare officer
}
`;

export default defineConfig(({ mode }) => {
const isDev = mode === "development";
/**
* Build a list of public files, which means all files in the
* src/components/ComponentName folders.
* Everything else will be moved to the chunk folder.
*/
const input = isDev
? {}
: Object.fromEntries(
glob
.sync("src/**/*.{ts,tsx}")
.filter((file) => {
return file.match(
/src\/components\/[A-Z][a-zA-Z]*\/[A-Z][a-zA-Z]*\.tsx/,
)
? file
: null;
})
.map((file) => {
return [
// This remove `src/` as well as the file extension from each
// file, so e.g. src/nested/foo.js becomes nested/foo
path.relative(
"src",
file.slice(0, file.length - path.extname(file).length),
),
// This expands the relative paths to absolute paths, so e.g.
// src/nested/foo becomes /project/src/nested/foo.js
fileURLToPath(new URL(file, import.meta.url)),
];
}),
);

return {
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname, "src/components/index.ts"),
formats: ["es"],
name: "UIButton",
},
rollupOptions: {
input: {
index: resolve(__dirname, "src/components/index.ts"),
style: resolve(__dirname, "src/style.ts"),
...input,
},
treeshake: "smallest",
external: externalDependencies,
output: {
compact: true,
minifyInternalExports: false,
assetFileNames: "style[extname]",
entryFileNames: "[name].js",
chunkFileNames: "chunks/[name].[hash].js",
banner: (module) => {
if (module?.facadeModuleId?.endsWith("src/components/index.ts")) {
return banner;
}
},
},
},
},
esbuild: {
supported: {
"top-level-await": true,
},
},
plugins: [],
};
});
28 changes: 28 additions & 0 deletions packages/ui-button/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/// <reference types="vitest" />

import { defineConfig, mergeConfig } from "vitest/config";

import viteConfig from "./vite.config";

export default defineConfig((configEnv) =>
mergeConfig(
viteConfig(configEnv),
defineConfig({
test: {
globals: true,
setupFiles: ["./vitest.setup.ts"],
environment: "happy-dom",
coverage: {
include: ["src/**/*.ts", "src/**/*.tsx", "!src/style.ts"],
provider: "v8",
thresholds: {
statements: 100,
branches: 100,
functions: 100,
lines: 100,
},
},
},
}),
),
);
17 changes: 17 additions & 0 deletions packages/ui-button/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import "@testing-library/jest-dom/vitest";

import util from "node:util";

const originalConsoleError = console.error;
console.error = (...args: any) => {
const message = util.format(...args);
if (
/(Warning: validateDOMNesting|Invalid prop|Failed prop type|React does not recognize|Unknown event handler property)/gi.test(
message,
)
) {
throw new Error(message);
} else {
originalConsoleError.apply(console, [...args]);
}
};
1 change: 1 addition & 0 deletions packages/ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"dependencies": {
"@floating-ui/react": "0.26.23",
"@tailwindcss/typography": "0.5.15",
"@versini/ui-button": "workspace:../ui-button",
"@versini/ui-hooks": "workspace:../ui-hooks",
"@versini/ui-icons": "workspace:../ui-icons",
"@versini/ui-private": "workspace:../ui-private",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-components/src/components/Anchor/Anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { ButtonLink } from "../Button/ButtonLink";
import { ButtonLink } from "@versini/ui-button";
import type { AnchorProps } from "./AnchorTypes";

export const Anchor = React.forwardRef<HTMLAnchorElement, AnchorProps>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ButtonLinkProps } from "../Button/ButtonTypes";
import type { ButtonLinkProps } from "@versini/ui-button";

export type AnchorProps = ButtonLinkProps &
React.AnchorHTMLAttributes<HTMLAnchorElement>;
3 changes: 2 additions & 1 deletion packages/ui-components/src/components/Bubble/Bubble.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ButtonIcon } from "@versini/ui-button";
import { IconCopied, IconCopy } from "@versini/ui-icons";
import { useEffect, useState } from "react";
import { ButtonIcon } from "..";

import { BubbleProps } from "./BubbleTypes";
import { getBubbleClasses } from "./utilities";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Button, ButtonIcon } from "@versini/ui-button";
import { IconSettings } from "@versini/ui-icons";

import { Button, ButtonIcon, Menu, MenuItem, MenuSeparator } from "../..";
import { Menu, MenuItem, MenuSeparator } from "../..";

const MENU_TRIGGER_LABEL = "Click Me";
const FIRST_MENU_ITEM = "Menu 1";
Expand Down
3 changes: 1 addition & 2 deletions packages/ui-components/src/components/Panel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { ButtonIcon } from "@versini/ui-button";
import { IconClose } from "@versini/ui-icons";

import { Suspense, lazy, useEffect, useRef } from "react";

import { ButtonIcon } from "../";
import type { PanelProps } from "./PanelTypes";
import { TYPE_PANEL, getPanelClassName } from "./utilities";

Expand Down
Loading

0 comments on commit 651f857

Please sign in to comment.