aboutsummaryrefslogtreecommitdiff
path: root/packages/memory-graph/src/ui
diff options
context:
space:
mode:
authornexxeln <[email protected]>2025-11-19 18:57:55 +0000
committernexxeln <[email protected]>2025-11-19 18:57:56 +0000
commit5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb (patch)
tree60336fd37b41e3597065729d098877483eba73b6 /packages/memory-graph/src/ui
parentFix: Prevent multiple prompts while AI response is generated (fixes #538) (#583) (diff)
downloadsupermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.tar.xz
supermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.zip
includes: - a package that contains a MemoryGraph component which handles fetching data and rendering the graph - a playground to test the package problems: - the bundle size is huge - the styles are kinda broken? we are using [https://www.npmjs.com/package/vite-plugin-libgi-inject-css](https://www.npmjs.com/package/vite-plugin-lib-inject-css) to inject the styles ![image.png](https://app.graphite.com/user-attachments/assets/cb1822c5-850a-45a2-9bfa-72b73436659f.png)
Diffstat (limited to 'packages/memory-graph/src/ui')
-rw-r--r--packages/memory-graph/src/ui/badge.css.ts119
-rw-r--r--packages/memory-graph/src/ui/badge.tsx27
-rw-r--r--packages/memory-graph/src/ui/button.css.ts210
-rw-r--r--packages/memory-graph/src/ui/button.tsx30
-rw-r--r--packages/memory-graph/src/ui/collapsible.tsx33
-rw-r--r--packages/memory-graph/src/ui/glass-effect.css.ts58
-rw-r--r--packages/memory-graph/src/ui/glass-effect.tsx21
-rw-r--r--packages/memory-graph/src/ui/heading.css.ts24
-rw-r--r--packages/memory-graph/src/ui/heading.tsx18
9 files changed, 540 insertions, 0 deletions
diff --git a/packages/memory-graph/src/ui/badge.css.ts b/packages/memory-graph/src/ui/badge.css.ts
new file mode 100644
index 00000000..1af96c1d
--- /dev/null
+++ b/packages/memory-graph/src/ui/badge.css.ts
@@ -0,0 +1,119 @@
+import { recipe, type RecipeVariants } from "@vanilla-extract/recipes";
+import { style, globalStyle } from "@vanilla-extract/css";
+import { themeContract } from "../styles/theme.css";
+
+/**
+ * Base styles for SVG icons inside badges
+ */
+export const badgeIcon = style({
+ width: "0.75rem",
+ height: "0.75rem",
+ pointerEvents: "none",
+});
+
+/**
+ * Badge recipe with variants
+ * Replaces CVA-based badge variants with vanilla-extract recipes
+ */
+const badgeBase = style({
+ display: "inline-flex",
+ alignItems: "center",
+ justifyContent: "center",
+ borderRadius: themeContract.radii.md,
+ border: "1px solid",
+ paddingLeft: themeContract.space[2],
+ paddingRight: themeContract.space[2],
+ paddingTop: "0.125rem",
+ paddingBottom: "0.125rem",
+ fontSize: themeContract.typography.fontSize.xs,
+ fontWeight: themeContract.typography.fontWeight.medium,
+ width: "fit-content",
+ whiteSpace: "nowrap",
+ flexShrink: 0,
+ gap: themeContract.space[1],
+ transition: "color 200ms ease-in-out, box-shadow 200ms ease-in-out",
+ overflow: "hidden",
+
+ selectors: {
+ "&:focus-visible": {
+ borderColor: themeContract.colors.accent.primary,
+ boxShadow: `0 0 0 2px ${themeContract.colors.accent.primary}33`,
+ },
+ "&[aria-invalid='true']": {
+ boxShadow: `0 0 0 2px ${themeContract.colors.status.forgotten}33`,
+ borderColor: themeContract.colors.status.forgotten,
+ },
+ },
+});
+
+// Global style for SVG children
+globalStyle(`${badgeBase} > svg`, {
+ width: "0.75rem",
+ height: "0.75rem",
+ pointerEvents: "none",
+});
+
+export const badge = recipe({
+ base: badgeBase,
+
+ variants: {
+ variant: {
+ default: {
+ borderColor: "transparent",
+ backgroundColor: themeContract.colors.accent.primary,
+ color: themeContract.colors.text.primary,
+
+ selectors: {
+ "a&:hover": {
+ opacity: 0.9,
+ },
+ },
+ },
+
+ secondary: {
+ borderColor: "transparent",
+ backgroundColor: themeContract.colors.background.secondary,
+ color: themeContract.colors.text.secondary,
+
+ selectors: {
+ "a&:hover": {
+ backgroundColor: themeContract.colors.background.accent,
+ },
+ },
+ },
+
+ destructive: {
+ borderColor: "transparent",
+ backgroundColor: themeContract.colors.status.forgotten,
+ color: themeContract.colors.text.primary,
+
+ selectors: {
+ "a&:hover": {
+ opacity: 0.9,
+ },
+ "&:focus-visible": {
+ boxShadow: `0 0 0 2px ${themeContract.colors.status.forgotten}33`,
+ },
+ },
+ },
+
+ outline: {
+ borderColor: themeContract.colors.document.border,
+ backgroundColor: "transparent",
+ color: themeContract.colors.text.primary,
+
+ selectors: {
+ "a&:hover": {
+ backgroundColor: themeContract.colors.document.primary,
+ },
+ },
+ },
+ },
+ },
+
+ defaultVariants: {
+ variant: "default",
+ },
+});
+
+export type BadgeVariants = RecipeVariants<typeof badge>;
diff --git a/packages/memory-graph/src/ui/badge.tsx b/packages/memory-graph/src/ui/badge.tsx
new file mode 100644
index 00000000..0708888f
--- /dev/null
+++ b/packages/memory-graph/src/ui/badge.tsx
@@ -0,0 +1,27 @@
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+import { badge, type BadgeVariants } from "./badge.css";
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ BadgeVariants & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ const combinedClassName = className
+ ? `${badge({ variant })} ${className}`
+ : badge({ variant });
+
+ return (
+ <Comp
+ className={combinedClassName}
+ data-slot="badge"
+ {...props}
+ />
+ );
+}
+
+export { Badge, badge as badgeVariants };
diff --git a/packages/memory-graph/src/ui/button.css.ts b/packages/memory-graph/src/ui/button.css.ts
new file mode 100644
index 00000000..ad9cce8c
--- /dev/null
+++ b/packages/memory-graph/src/ui/button.css.ts
@@ -0,0 +1,210 @@
+import { recipe, type RecipeVariants } from "@vanilla-extract/recipes";
+import { style } from "@vanilla-extract/css";
+import { themeContract } from "../styles/theme.css";
+
+/**
+ * Base styles for SVG icons inside buttons
+ */
+export const buttonIcon = style({
+ pointerEvents: "none",
+ flexShrink: 0,
+ selectors: {
+ "&:not([class*='size-'])": {
+ width: "1rem",
+ height: "1rem",
+ },
+ },
+});
+
+/**
+ * Button recipe with variants
+ * Replaces CVA-based button variants with vanilla-extract recipes
+ */
+export const button = recipe({
+ base: {
+ display: "inline-flex",
+ alignItems: "center",
+ justifyContent: "center",
+ gap: themeContract.space[2],
+ whiteSpace: "nowrap",
+ borderRadius: themeContract.radii.md,
+ fontSize: themeContract.typography.fontSize.sm,
+ fontWeight: themeContract.typography.fontWeight.medium,
+ transition: themeContract.transitions.normal,
+ flexShrink: 0,
+ outline: "none",
+ border: "1px solid transparent",
+ cursor: "pointer",
+
+ // SVG sizing
+ selectors: {
+ [`&:has(${buttonIcon})`]: {
+ // Buttons with icons get adjusted padding
+ },
+ "&:disabled": {
+ pointerEvents: "none",
+ opacity: 0.5,
+ },
+ "&:focus-visible": {
+ borderColor: themeContract.colors.accent.primary,
+ boxShadow: `0 0 0 2px ${themeContract.colors.accent.primary}33`,
+ },
+ "&[aria-invalid='true']": {
+ boxShadow: `0 0 0 2px ${themeContract.colors.status.forgotten}`,
+ borderColor: themeContract.colors.status.forgotten,
+ },
+ },
+ },
+
+ variants: {
+ variant: {
+ default: {
+ backgroundColor: themeContract.colors.accent.primary,
+ color: themeContract.colors.text.primary,
+ boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ backgroundColor: themeContract.colors.accent.secondary,
+ },
+ },
+ },
+
+ destructive: {
+ backgroundColor: themeContract.colors.status.forgotten,
+ color: themeContract.colors.text.primary,
+ boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ opacity: 0.9,
+ },
+ "&:focus-visible": {
+ boxShadow: `0 0 0 2px ${themeContract.colors.status.forgotten}33`,
+ },
+ },
+ },
+
+ outline: {
+ backgroundColor: themeContract.colors.background.primary,
+ borderColor: themeContract.colors.document.border,
+ color: themeContract.colors.text.primary,
+ boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ backgroundColor: themeContract.colors.document.primary,
+ },
+ },
+ },
+
+ secondary: {
+ backgroundColor: themeContract.colors.background.secondary,
+ color: themeContract.colors.text.secondary,
+ boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ backgroundColor: themeContract.colors.background.accent,
+ },
+ },
+ },
+
+ ghost: {
+ backgroundColor: "transparent",
+ color: themeContract.colors.text.primary,
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ backgroundColor: themeContract.colors.document.primary,
+ },
+ },
+ },
+
+ link: {
+ backgroundColor: "transparent",
+ color: themeContract.colors.accent.primary,
+ textDecoration: "underline",
+ textUnderlineOffset: "4px",
+
+ selectors: {
+ "&:hover:not(:disabled)": {
+ textDecoration: "underline",
+ },
+ },
+ },
+
+ settingsNav: {
+ cursor: "pointer",
+ borderRadius: themeContract.radii.sm,
+ backgroundColor: "transparent",
+ color: themeContract.colors.text.primary,
+ },
+ },
+
+ size: {
+ default: {
+ height: "36px",
+ paddingLeft: themeContract.space[4],
+ paddingRight: themeContract.space[4],
+ paddingTop: themeContract.space[2],
+ paddingBottom: themeContract.space[2],
+
+ selectors: {
+ "&:has(svg)": {
+ paddingLeft: themeContract.space[3],
+ paddingRight: themeContract.space[3],
+ },
+ },
+ },
+
+ sm: {
+ height: "32px",
+ borderRadius: themeContract.radii.md,
+ gap: themeContract.space[1],
+ paddingLeft: themeContract.space[3],
+ paddingRight: themeContract.space[3],
+
+ selectors: {
+ "&:has(svg)": {
+ paddingLeft: themeContract.space[2],
+ paddingRight: themeContract.space[2],
+ },
+ },
+ },
+
+ lg: {
+ height: "40px",
+ borderRadius: themeContract.radii.md,
+ paddingLeft: themeContract.space[6],
+ paddingRight: themeContract.space[6],
+
+ selectors: {
+ "&:has(svg)": {
+ paddingLeft: themeContract.space[4],
+ paddingRight: themeContract.space[4],
+ },
+ },
+ },
+
+ icon: {
+ width: "36px",
+ height: "36px",
+ padding: 0,
+ },
+
+ settingsNav: {
+ height: "32px",
+ gap: 0,
+ padding: 0,
+ },
+ },
+ },
+
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+});
+
+export type ButtonVariants = RecipeVariants<typeof button>;
diff --git a/packages/memory-graph/src/ui/button.tsx b/packages/memory-graph/src/ui/button.tsx
new file mode 100644
index 00000000..031f2cc8
--- /dev/null
+++ b/packages/memory-graph/src/ui/button.tsx
@@ -0,0 +1,30 @@
+import { Slot } from "@radix-ui/react-slot";
+import type * as React from "react";
+import { button, type ButtonVariants } from "./button.css";
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ ButtonVariants & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+
+ const combinedClassName = className
+ ? `${button({ variant, size })} ${className}`
+ : button({ variant, size });
+
+ return (
+ <Comp
+ className={combinedClassName}
+ data-slot="button"
+ {...props}
+ />
+ );
+}
+
+export { Button, button as buttonVariants };
diff --git a/packages/memory-graph/src/ui/collapsible.tsx b/packages/memory-graph/src/ui/collapsible.tsx
new file mode 100644
index 00000000..0551ffdd
--- /dev/null
+++ b/packages/memory-graph/src/ui/collapsible.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
+
+function Collapsible({
+ ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
+ return (
+ <CollapsiblePrimitive.CollapsibleTrigger
+ data-slot="collapsible-trigger"
+ {...props}
+ />
+ );
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
+ return (
+ <CollapsiblePrimitive.CollapsibleContent
+ data-slot="collapsible-content"
+ {...props}
+ />
+ );
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/packages/memory-graph/src/ui/glass-effect.css.ts b/packages/memory-graph/src/ui/glass-effect.css.ts
new file mode 100644
index 00000000..16e0fcdc
--- /dev/null
+++ b/packages/memory-graph/src/ui/glass-effect.css.ts
@@ -0,0 +1,58 @@
+import { style } from "@vanilla-extract/css";
+import { recipe } from "@vanilla-extract/recipes";
+import { themeContract } from "../styles/theme.css";
+
+/**
+ * Glass menu effect container
+ */
+export const glassMenuContainer = style({
+ position: "absolute",
+ inset: 0,
+});
+
+/**
+ * Glass menu effect with customizable border radius
+ */
+export const glassMenuEffect = recipe({
+ base: {
+ position: "absolute",
+ inset: 0,
+ backdropFilter: "blur(12px)",
+ WebkitBackdropFilter: "blur(12px)",
+ background: "rgba(255, 255, 255, 0.05)",
+ border: `1px solid ${themeContract.colors.document.border}`,
+ },
+
+ variants: {
+ rounded: {
+ none: {
+ borderRadius: themeContract.radii.none,
+ },
+ sm: {
+ borderRadius: themeContract.radii.sm,
+ },
+ md: {
+ borderRadius: themeContract.radii.md,
+ },
+ lg: {
+ borderRadius: themeContract.radii.lg,
+ },
+ xl: {
+ borderRadius: themeContract.radii.xl,
+ },
+ "2xl": {
+ borderRadius: themeContract.radii["2xl"],
+ },
+ "3xl": {
+ borderRadius: "1.5rem", // Tailwind's rounded-3xl
+ },
+ full: {
+ borderRadius: themeContract.radii.full,
+ },
+ },
+ },
+
+ defaultVariants: {
+ rounded: "3xl",
+ },
+});
diff --git a/packages/memory-graph/src/ui/glass-effect.tsx b/packages/memory-graph/src/ui/glass-effect.tsx
new file mode 100644
index 00000000..e1908f52
--- /dev/null
+++ b/packages/memory-graph/src/ui/glass-effect.tsx
@@ -0,0 +1,21 @@
+import {
+ glassMenuContainer,
+ glassMenuEffect,
+} from "./glass-effect.css";
+
+interface GlassMenuEffectProps {
+ rounded?: "none" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "full";
+ className?: string;
+}
+
+export function GlassMenuEffect({
+ rounded = "3xl",
+ className = "",
+}: GlassMenuEffectProps) {
+ return (
+ <div className={`${glassMenuContainer} ${className}`}>
+ {/* Frosted glass effect with translucent border */}
+ <div className={glassMenuEffect({ rounded })} />
+ </div>
+ );
+}
diff --git a/packages/memory-graph/src/ui/heading.css.ts b/packages/memory-graph/src/ui/heading.css.ts
new file mode 100644
index 00000000..128d97a6
--- /dev/null
+++ b/packages/memory-graph/src/ui/heading.css.ts
@@ -0,0 +1,24 @@
+import { style } from "@vanilla-extract/css";
+import { themeContract } from "../styles/theme.css";
+
+/**
+ * Responsive heading style with bold weight
+ */
+export const headingH3Bold = style({
+ fontSize: "0.625rem", // 10px
+ fontWeight: themeContract.typography.fontWeight.bold,
+ lineHeight: "28px",
+ letterSpacing: "-0.4px",
+
+ "@media": {
+ "screen and (min-width: 640px)": {
+ fontSize: themeContract.typography.fontSize.xs, // 12px
+ },
+ "screen and (min-width: 768px)": {
+ fontSize: themeContract.typography.fontSize.sm, // 14px
+ },
+ "screen and (min-width: 1024px)": {
+ fontSize: themeContract.typography.fontSize.base, // 16px
+ },
+ },
+});
diff --git a/packages/memory-graph/src/ui/heading.tsx b/packages/memory-graph/src/ui/heading.tsx
new file mode 100644
index 00000000..65e8abc8
--- /dev/null
+++ b/packages/memory-graph/src/ui/heading.tsx
@@ -0,0 +1,18 @@
+import { Root } from "@radix-ui/react-slot";
+import { headingH3Bold } from "./heading.css";
+
+export function HeadingH3Bold({
+ className,
+ asChild,
+ ...props
+}: React.ComponentProps<"h3"> & { asChild?: boolean }) {
+ const Comp = asChild ? Root : "h3";
+
+ const combinedClassName = className
+ ? `${headingH3Bold} ${className}`
+ : headingH3Bold;
+
+ return (
+ <Comp className={combinedClassName} {...props} />
+ );
+}