svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
- className,
- )}
- {...props}
- />
- );
-}
-
-export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage };
+export { Avatar, AvatarFallback, AvatarImage };
diff --git a/packages/ui/src/components/button-group.test.tsx b/packages/ui/src/components/button-group.test.tsx
index d8da8f1f1..9070c34e7 100644
--- a/packages/ui/src/components/button-group.test.tsx
+++ b/packages/ui/src/components/button-group.test.tsx
@@ -1,6 +1,6 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
-import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "./button-group";
+import { ButtonGroup } from "./button-group";
describe("ButtonGroup", () => {
it("renders a fieldset with data-slot='button-group'", () => {
@@ -25,37 +25,3 @@ describe("ButtonGroup", () => {
expect(screen.getByTestId("g")).toHaveClass("my-custom");
});
});
-
-describe("ButtonGroupText", () => {
- it("renders content inside a div by default", () => {
- render(
Hello);
- expect(screen.getByText("Hello").tagName).toBe("DIV");
- });
-
- it("merges custom className", () => {
- render(
x);
- expect(screen.getByText("x")).toHaveClass("my-class");
- });
-
- it("supports custom render function", () => {
- render(
}>label);
- expect(screen.getByText("label").tagName).toBe("SPAN");
- });
-});
-
-describe("ButtonGroupSeparator", () => {
- it("defaults to vertical orientation", () => {
- render(
);
- expect(screen.getByTestId("sep")).toHaveAttribute("data-orientation", "vertical");
- });
-
- it("supports horizontal orientation", () => {
- render(
);
- expect(screen.getByTestId("sep")).toHaveAttribute("data-orientation", "horizontal");
- });
-
- it("uses data-slot='button-group-separator'", () => {
- render(
);
- expect(screen.getByTestId("sep")).toHaveAttribute("data-slot", "button-group-separator");
- });
-});
diff --git a/packages/ui/src/components/button-group.tsx b/packages/ui/src/components/button-group.tsx
index f5f5fdabf..b6cc0d33b 100644
--- a/packages/ui/src/components/button-group.tsx
+++ b/packages/ui/src/components/button-group.tsx
@@ -1,8 +1,5 @@
import type { VariantProps } from "class-variance-authority";
-import { mergeProps } from "@base-ui/react/merge-props";
-import { useRender } from "@base-ui/react/use-render";
import { cva } from "class-variance-authority";
-import { Separator } from "@reactive-resume/ui/components/separator";
import { cn } from "@reactive-resume/utils/style";
const buttonGroupVariants = cva(
@@ -37,41 +34,4 @@ function ButtonGroup({
);
}
-function ButtonGroupText({ className, render, ...props }: useRender.ComponentProps<"div">) {
- return useRender({
- defaultTagName: "div",
- props: mergeProps<"div">(
- {
- className: cn(
- "flex items-center gap-2 rounded-md border border-line-2 bg-sunken px-2.5 font-medium text-ink-2 text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
- className,
- ),
- },
- props,
- ),
- render,
- state: {
- slot: "button-group-text",
- },
- });
-}
-
-function ButtonGroupSeparator({
- className,
- orientation = "vertical",
- ...props
-}: React.ComponentProps
) {
- return (
-
- );
-}
-
-export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };
+export { ButtonGroup, buttonGroupVariants };
diff --git a/packages/ui/src/components/input-group.test.tsx b/packages/ui/src/components/input-group.test.tsx
index 58cddce1b..273788be9 100644
--- a/packages/ui/src/components/input-group.test.tsx
+++ b/packages/ui/src/components/input-group.test.tsx
@@ -3,14 +3,7 @@ import userEvent from "@testing-library/user-event";
import { describe, expect, it, test } from "vitest";
import { createPortal } from "react-dom";
import { FormControl, FormItem, FormLabel } from "./form";
-import {
- InputGroup,
- InputGroupAddon,
- InputGroupButton,
- InputGroupInput,
- InputGroupText,
- InputGroupTextarea,
-} from "./input-group";
+import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText } from "./input-group";
function PortaledInput() {
return createPortal(, document.body);
@@ -203,12 +196,3 @@ describe("InputGroupInput", () => {
expect(screen.getByTestId("i")).toHaveAttribute("data-slot", "input-group-control");
});
});
-
-describe("InputGroupTextarea", () => {
- it("uses data-slot='input-group-control' and renders textarea", () => {
- render();
- const t = screen.getByTestId("t");
- expect(t.tagName).toBe("TEXTAREA");
- expect(t).toHaveAttribute("data-slot", "input-group-control");
- });
-});
diff --git a/packages/ui/src/components/input-group.tsx b/packages/ui/src/components/input-group.tsx
index 60f3d90f5..bc6189ff8 100644
--- a/packages/ui/src/components/input-group.tsx
+++ b/packages/ui/src/components/input-group.tsx
@@ -4,7 +4,6 @@ import { cva } from "class-variance-authority";
import { Button } from "@reactive-resume/ui/components/button";
import { useFormControl } from "@reactive-resume/ui/components/form";
import { Input } from "@reactive-resume/ui/components/input";
-import { Textarea } from "@reactive-resume/ui/components/textarea";
import { cn } from "@reactive-resume/utils/style";
function InputGroup({
@@ -161,31 +160,4 @@ function InputGroupInput({
);
}
-function InputGroupTextarea({
- className,
- id: idProp,
- "aria-describedby": ariaDescribedByProp,
- "aria-invalid": ariaInvalidProp,
- ...props
-}: React.ComponentProps<"textarea">) {
- const formControl = useFormControl();
- const controlId = idProp ?? formControl.id;
- const describedBy = ariaDescribedByProp ?? formControl["aria-describedby"];
- const invalid = ariaInvalidProp ?? formControl["aria-invalid"];
-
- return (
-
- );
-}
-
-export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea };
+export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText };
diff --git a/packages/ui/src/components/kbd.test.tsx b/packages/ui/src/components/kbd.test.tsx
index 5cbab13fa..76d35c397 100644
--- a/packages/ui/src/components/kbd.test.tsx
+++ b/packages/ui/src/components/kbd.test.tsx
@@ -1,6 +1,6 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
-import { Kbd, KbdGroup } from "./kbd";
+import { Kbd } from "./kbd";
describe("Kbd", () => {
it("renders as a element with data-slot='kbd'", () => {
@@ -24,32 +24,3 @@ describe("Kbd", () => {
expect(screen.getByTestId("k")).toHaveClass("my-class");
});
});
-
-describe("KbdGroup", () => {
- it("renders as a wrapper with data-slot='kbd-group'", () => {
- render(x);
- const g = screen.getByTestId("g");
- expect(g.tagName).toBe("KBD");
- expect(g).toHaveAttribute("data-slot", "kbd-group");
- });
-
- it("groups multiple Kbd elements", () => {
- render(
-
- Ctrl
- K
- ,
- );
- expect(screen.getByText("Ctrl")).toBeInTheDocument();
- expect(screen.getByText("K")).toBeInTheDocument();
- });
-
- it("merges custom className", () => {
- render(
-
- x
- ,
- );
- expect(screen.getByTestId("g")).toHaveClass("my-group");
- });
-});
diff --git a/packages/ui/src/components/kbd.tsx b/packages/ui/src/components/kbd.tsx
index 346e94bc5..491b600b3 100644
--- a/packages/ui/src/components/kbd.tsx
+++ b/packages/ui/src/components/kbd.tsx
@@ -13,8 +13,4 @@ function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
);
}
-function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
- return ;
-}
-
-export { Kbd, KbdGroup };
+export { Kbd };
diff --git a/packages/ui/src/components/popover.test.tsx b/packages/ui/src/components/popover.test.tsx
index 35a565999..dca6f8aef 100644
--- a/packages/ui/src/components/popover.test.tsx
+++ b/packages/ui/src/components/popover.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
-import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from "./popover";
+import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "./popover";
describe("Popover", () => {
it("trigger element has data-slot='popover-trigger'", () => {
@@ -78,20 +78,6 @@ describe("PopoverTitle", () => {
});
});
-describe("PopoverDescription", () => {
- it("uses data-slot='popover-description'", () => {
- render(
-
- Open
-
- My description
-
- ,
- );
- expect(screen.getByText("My description")).toHaveAttribute("data-slot", "popover-description");
- });
-});
-
describe("PopoverContent", () => {
it("uses data-slot='popover-content'", () => {
render(
diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx
index 92f6594b2..3776f1e20 100644
--- a/packages/ui/src/components/popover.tsx
+++ b/packages/ui/src/components/popover.tsx
@@ -51,10 +51,4 @@ function PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {
);
}
-function PopoverDescription({ className, ...props }: PopoverPrimitive.Description.Props) {
- return (
-
- );
-}
-
-export { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger };
+export { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger };
diff --git a/packages/utils/src/locale.ts b/packages/utils/src/locale.ts
index ba8b41e20..328b9acc3 100644
--- a/packages/utils/src/locale.ts
+++ b/packages/utils/src/locale.ts
@@ -67,10 +67,6 @@ export function isLocale(value: unknown): value is Locale {
return localeSchema.safeParse(value).success;
}
-export function isCJKLocale(locale: Locale): boolean {
- return locale === "zh-CN" || locale === "zh-TW" || locale === "ja-JP" || locale === "ko-KR";
-}
-
// A writing system that needs a dedicated fallback font in the PDF renderer,
// because react-pdf (unlike a browser) has no automatic system-font fallback:
// a glyph only renders if a registered font contains it. We pick the matching
diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts
index 6e6dd965b..68a535bf2 100644
--- a/packages/utils/src/string.ts
+++ b/packages/utils/src/string.ts
@@ -66,6 +66,15 @@ export function generateRandomName() {
* @param html - The HTML string to strip.
* @returns The text content without HTML tags.
*/
+/** Text safe to place inside HTML: markup characters and both quote marks become entities. */
+export const escapeHtml = (text: string) =>
+ text
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'");
+
export function stripHtml(html: string | undefined): string {
if (!html) return "";
return html.replace(/<[^>]*>/g, "").trim();
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6dbd2875b..64becd1ef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -386,9 +386,6 @@ importers:
css-tree:
specifier: ^3.2.1
version: 3.2.1
- deepmerge-ts:
- specifier: ^8.0.2
- version: 8.0.2
drizzle-orm:
specifier: 1.0.0-rc.4
version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.0)(zod@4.6.5)
@@ -1337,6 +1334,9 @@ importers:
'@reactive-resume/schema':
specifier: workspace:*
version: link:../schema
+ '@reactive-resume/utils':
+ specifier: workspace:*
+ version: link:../utils
css-tree:
specifier: ^3.2.1
version: 3.2.1
@@ -5883,10 +5883,6 @@ packages:
babel-plugin-macros:
optional: true
- deepmerge-ts@8.0.2:
- resolution: {integrity: sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==}
- engines: {node: '>=16.9.0'}
-
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@@ -13085,8 +13081,6 @@ snapshots:
optionalDependencies:
babel-plugin-macros: 3.1.0
- deepmerge-ts@8.0.2: {}
-
deepmerge@4.3.1: {}
default-browser-id@5.0.1: {}
diff --git a/tooling/turbo/source-cache.test.ts b/tooling/turbo/source-cache.test.ts
index 68309efe4..aa6f1d6e1 100644
--- a/tooling/turbo/source-cache.test.ts
+++ b/tooling/turbo/source-cache.test.ts
@@ -7,7 +7,7 @@ import { afterAll, beforeAll, expect, it } from "vitest";
const root = fileURLToPath(new URL("../../", import.meta.url));
const turbo = join(root, "node_modules", "turbo", "bin", "turbo");
-const tasks = ["build", "check", "typecheck", "test", "test:coverage", "test:ci", "test:agent"];
+const tasks = ["build", "typecheck", "test", "test:coverage", "test:ci", "test:agent"];
type DryTask = { taskId: string; task: string; hash: string; command: string; dependencies: string[] };
let directory: string;
let baseline: Map;
diff --git a/turbo.json b/turbo.json
index 36a00bee8..574528bbb 100644
--- a/turbo.json
+++ b/turbo.json
@@ -119,9 +119,6 @@
"outputs": ["dist/**", ".vercel/**"],
"dependsOn": ["transit"]
},
- "check": {
- "dependsOn": ["transit"]
- },
"typecheck": {
"dependsOn": ["transit"]
},
@@ -134,9 +131,6 @@
"test:ci": {
"dependsOn": ["transit"]
},
- "test:e2e": {
- "cache": false
- },
"test:agent": {
"dependsOn": ["transit"]
},