diff options
| author | nexxeln <[email protected]> | 2025-11-19 18:57:55 +0000 |
|---|---|---|
| committer | nexxeln <[email protected]> | 2025-11-19 18:57:56 +0000 |
| commit | 5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb (patch) | |
| tree | 60336fd37b41e3597065729d098877483eba73b6 /packages/memory-graph/src/ui | |
| parent | Fix: Prevent multiple prompts while AI response is generated (fixes #538) (#583) (diff) | |
| download | supermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.tar.xz supermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.zip | |
package the graph (#563)shoubhit/eng-358-packaging-graph-component
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

Diffstat (limited to 'packages/memory-graph/src/ui')
| -rw-r--r-- | packages/memory-graph/src/ui/badge.css.ts | 119 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/badge.tsx | 27 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/button.css.ts | 210 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/button.tsx | 30 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/collapsible.tsx | 33 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/glass-effect.css.ts | 58 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/glass-effect.tsx | 21 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/heading.css.ts | 24 | ||||
| -rw-r--r-- | packages/memory-graph/src/ui/heading.tsx | 18 |
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} /> + ); +} |