diff --git a/packages/components/package.json b/packages/components/package.json
index fc963aa7..e7cea2e5 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -43,6 +43,7 @@
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
+ "@dolthub/web-utils": "^0.1.2",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.5",
diff --git a/packages/components/src/CharCount/index.module.css b/packages/components/src/CharCount/index.module.css
new file mode 100644
index 00000000..98eca461
--- /dev/null
+++ b/packages/components/src/CharCount/index.module.css
@@ -0,0 +1,7 @@
+.charCount {
+ @apply text-acc-grey;
+}
+
+.charCountOver {
+ @apply text-acc-red;
+}
diff --git a/packages/components/src/CharCount/index.tsx b/packages/components/src/CharCount/index.tsx
new file mode 100644
index 00000000..69fe26fe
--- /dev/null
+++ b/packages/components/src/CharCount/index.tsx
@@ -0,0 +1,22 @@
+import cx from "classnames";
+import React from "react";
+import css from "./index.module.css";
+
+export const maxChar = 2048;
+
+type Props = {
+ desc: string;
+ className?: string;
+};
+
+export default function CharCount(props: Props) {
+ return (
+ maxChar,
+ })}
+ >
+ {props.desc.length}/{maxChar}
+
+ );
+}
diff --git a/packages/components/src/__tests__/CharCount.test.tsx b/packages/components/src/__tests__/CharCount.test.tsx
new file mode 100644
index 00000000..a209bb1c
--- /dev/null
+++ b/packages/components/src/__tests__/CharCount.test.tsx
@@ -0,0 +1,30 @@
+import { nTimes } from "@dolthub/web-utils";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import CharCount from "../CharCount";
+import css from "./index.module.css";
+
+const tilde = () => "~";
+
+const mocks = [
+ { words: "", over: false },
+ { words: nTimes(9, tilde).join(""), over: false },
+ { words: nTimes(2050, tilde).join(""), over: true },
+];
+
+describe("test CharCount", () => {
+ mocks.forEach(mock => {
+ it(`renders CharCount for word length of ${mock.words.length}`, () => {
+ render();
+
+ const content = screen.getByText(`${mock.words.length}/2048`);
+ expect(content).toBeVisible();
+ expect(content).toHaveClass(css.charCount);
+ expect(content).toHaveClass("classname");
+
+ if (mock.over) {
+ expect(content).toHaveClass(css.charCountOver);
+ }
+ });
+ });
+});
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 3e2fe954..4f437a09 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -1,3 +1,4 @@
+export { default as CharCount } from "./CharCount";
export { default as ExternalLink } from "./ExternalLink";
export { default as Loader } from "./Loader";
export { default as Popup, PopupProps } from "./Popup";
diff --git a/packages/components/tailwind.config.ts b/packages/components/tailwind.config.ts
index 0829ace9..2ba68554 100644
--- a/packages/components/tailwind.config.ts
+++ b/packages/components/tailwind.config.ts
@@ -1,3 +1,8 @@
+const colors = {
+ "acc-grey": "#b2c0c4",
+ "acc-red": "#ff9a99",
+};
+
const config = {
corePlugins: {
preflight: false,
@@ -6,6 +11,8 @@ const config = {
theme: {
extend: {
transitionProperty: { width: "width" },
+ gradientColorStops: colors,
+ colors,
fontFamily: {
sans: ["Source Sans Pro", "sans-serif"],
},
diff --git a/yarn.lock b/yarn.lock
index dad6a6cf..62397f44 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1919,6 +1919,7 @@ __metadata:
"@babel/preset-env": "npm:^7.23.9"
"@babel/preset-react": "npm:^7.23.3"
"@babel/preset-typescript": "npm:^7.23.3"
+ "@dolthub/web-utils": "npm:^0.1.2"
"@rollup/plugin-commonjs": "npm:^25.0.7"
"@rollup/plugin-node-resolve": "npm:^15.2.3"
"@rollup/plugin-typescript": "npm:^11.1.5"