diff --git a/.gitignore b/.gitignore
index 5ef6a520..c59e31c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+.aider*
+components-autogen
diff --git a/README.md b/README.md
index df191aa9..72330c4a 100644
--- a/README.md
+++ b/README.md
@@ -155,6 +155,81 @@ This project supports integration with Figma's Dev Mode MCP (Model Context Proto
For more detailed instructions, refer to [Figma's official Dev Mode MCP Server guide](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server).
+# Plasmic integration
+
+Plasmic integration adds a two-way sync to and from the attached Plasmic Studio project.
+Project ref is hard-coded in [./lib/plasmic-init.ts](./lib/plasmic-init.ts) and in [./plasmic.json](./plasmic.json)
+
+Plasmic integration adds `/plasmic-host` and `/plasmic/*` routes via a page router, along existing app router (the two are able to co-exist so long as they do not define overlapping routes, which is something we have to be careful about here).
+Generally, the first of the two routes above is responsible for localdev->PlasmicStudio sync direction, while the second is responsible for PlasmicStudio->localdev direction of sync.
+
+The main PlasmicUI built page lives [http://localhost:3000/plasmic/showcase](http://localhost:3000/plasmic/showcase).
+You can edit this page in the [shared plasmic project](https://studio.plasmic.app/projects/qkC7iPT4FekKxstnmsJETj)
+The page is rendered via plasmic's [catchall renderer](./pages/plasmic/[[...catchall]].tsx).
+
+## Dev workflow with Plasmic UI
+
+- Have `pnpm run dev` in one terminal.
+- Open http://localhost:3000/ for list of components
+- Open http://localhost:3000/plasmic-host and confirm "Your app is ready to host Plasmic Studio!" message
+- Open http://localhost:3000/plasmic/showcase for latest Plasmic-studio designed homepage
+- (done once or when switching Plasmic studio projects)
+ - Update [./lib/plasmic-init.ts](./lib/plasmic-init.ts) with my new project ID and token.
+ - Delete [./components/plasmic-autogen/](./components/plasmic-autogen/) and [./plasmic.json](./plasmic.json)
+ - `npx plasmic sync --projects [PROJECT_ID]`
+ - Prepare corresponding project in Plasmic Studio UI.
+ - Open the project, under kebab menu next to project name on the top right, click on "Configure custom app host"
+ - Put `http://localhost:3000/plasmic-host` as app host
+ - Hard-refresh the Plasmic Studio page. You'll see requests in terminal and will see registered code components in the plasmic UI
+
+## Issues
+
+Issue 1: Note that as more pages are created in the Plasmic Studio editor, those get synced into the codebase directly under `/pages` under
+corresponding global routes. For some reason these are broken and I have not spent time trying to fix them
+
+Issue 2: Note: please do not create a page with root URL ("/"). This causes a duplicate route issue in the componend library code project as plasmic pages under /plasmic/[PAGE] are rendered via the page router, while
+the rest of the components are rendered via the app router and app router already has a route for root URL.
+
+Issue 3: Plasmic editor Actions API still uses ReactDOM.render API which was removed in React 19, that is why I downgraded the project to React 18, so integration does not break until this gets fixed in he upstream
+
+## How to add new code components
+
+Follow existing examples in [./lib/plasmic-init-client.tsx](./lib/plasmic-init-client.tsx).
+
+## Using Plasmic Components
+
+For each component, Plasmic generates two React components
+in two files. For example, for component Homepage, there
+are:
+
+- A blackbox component at plasmic-autogen/website_starter/PlasmicHomepage.tsx
+ This is a blackbox, purely-presentational library component
+ that you can use to render your designs. This file is owned
+ by Plasmic, and you should not edit it -- it will be
+ overwritten when the component design is updated. This
+ component should only be used by the "wrapper" component
+ (below).
+
+- A wrapper component at ../pages/index.tsx
+ This component is owned and edited by you to instantiate the
+ PlasmicHomepage component with desired variants, states,
+ event handlers, and data. You have complete control over
+ this file, and this is the actual component that should be
+ used by the rest of the codebase.
+
+Learn more at https://www.plasmic.app/learn/codegen-guide/
+
+## Using Icons
+
+For each SVG icon, Plasmic also generates a React component.
+The component takes in all the usual props that you can pass
+to an svg element, and defaults to width/height of 1em.
+
+For example, for the CircleIcon icon at plasmic-autogen/website_starter/icons/PlasmicIcon\_\_Circle.tsx,
+instantiate it like:
+
+
+
## TODO
Coming up:
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 3b910158..0c8385ad 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,10 +10,10 @@ const compat = new FlatCompat({
});
const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+ // ...compat.extends("next/typescript"),
{
rules: {
- "@next/next/no-img-element": "off",
+ // "@next/next/no-img-element": "off",
},
},
];
diff --git a/lib/plasmic-component-registrations/GENERATION_INSTRUCTIONS.md b/lib/plasmic-component-registrations/GENERATION_INSTRUCTIONS.md
new file mode 100644
index 00000000..02aa855f
--- /dev/null
+++ b/lib/plasmic-component-registrations/GENERATION_INSTRUCTIONS.md
@@ -0,0 +1,117 @@
+Your goal is to integrate several components from Lantern's component library
+Use ./testimonial.tsx as a readonly blueprint for the file you'll generate
+
+The generated file will refer to existing components, include them and register them for plasmic use. Make sure the generated plasmic integration code is clean, does not have repetition
+and uses common general prop type for all components.
+if necessary, modify the base components to fit the general structure. use the readonly component examples as well as the DIFF snippets below to understand what needs to change in component source code:
+
+## Snippets for reference
+
+### snippet 1
+
+```diff
+ {(role || companyName) && (
+
+- {[role, companyName].filter(Boolean).join(", ")}
++ {role}
++ {role && companyName && ", "}
++ {companyName}
+
+ )}
+```
+
+### snippet 2
+
+```diff
+ /**
+ * Testimonial07: Center-aligned 5-star testimonial with bold quote, avatar, attribution, and company logo.
+@@ -24,12 +14,12 @@ export function Testimonial07({
+ content,
+ name,
+ role,
++ avatar,
+ companyName,
+ companyLogo,
+- image,
+ className,
+ ...props
+-}: Testimonial07Props) {
++}: TestimonialProps) {
+ return (
+
+ {/* Avatar */}
+-
++ {avatar}
+ {/* Name/Role/Company */}
+
+ {name}
+@@ -73,12 +57,16 @@ export function Testimonial07({
+ {/* Company Logo */}
+ {companyLogo && (
+
+```
+
+### snippet 3
+
+```diff
+-
+-export interface Testimonial09Props
+- extends React.HTMLAttributes {
+- content: string;
+- name: string;
+- role?: string;
+- companyName?: string;
+- image?: string;
+- className?: string;
+-}
++import { TestimonialProps } from "./testimonial-base";
+
+ /**
+ * Testimonial09: Card-style testimonial matching screenshot visual details.
+@@ -24,10 +15,10 @@ export function Testimonial09({
+ name,
+ role,
+ companyName,
+- image,
++ avatar,
+ className,
+ ...props
+-}: Testimonial09Props) {
++}: TestimonialProps) {
+ return (
+```
+
+## Notes to consider when writing code:
+
+1. Make sure social links are separate props (type string, not slot), which will not be shown if the link is missing but those are individual components and not an object
+2. If in any component before plasmic edits a prop is rendered in a
tag and you decide to convert that prop to a slot that takes in a react element, then make sure to switch the outer tag from
to
or .
tags expect flat text inside and so we cannot render full html subtree under them
+3. If there is a sub-component on the component being converted that can be used in a standalone manner (e.g. one similar to ProfileAvatar. Other examples: SocialLinks, )
diff --git a/lib/plasmic-component-registrations/component-with-variants.tsx b/lib/plasmic-component-registrations/component-with-variants.tsx
new file mode 100644
index 00000000..8c1ed364
--- /dev/null
+++ b/lib/plasmic-component-registrations/component-with-variants.tsx
@@ -0,0 +1,135 @@
+import {
+ ActionProps,
+ usePlasmicCanvasComponentInfo,
+} from "@plasmicapp/react-web/lib/host";
+import { useRef, useState } from "react";
+
+export type ComponentMap = Record>;
+
+export function getComponentAndControls(
+ variants: ComponentMap,
+ defaultVariant = "01"
+) {
+ type ComponentKind = keyof typeof variants;
+ function Component({
+ kind,
+ ...props
+ }: ComponentProps & {
+ kind: K;
+ }) {
+ const comp = variants[kind];
+ const { isSelected, ...rest } = usePlasmicCanvasComponentInfo(props) ?? {};
+ props.selectedInEditor = isSelected;
+ return comp(props);
+ }
+
+ const ComponentControl: React.ComponentType<
+ ActionProps
+ > = ({ studioOps, componentProps }) => {
+ const [hoveredKind, setHoveredKind] = useState(null);
+ const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
+ const containerRef = useRef(null);
+
+ const kinds = Object.keys(variants) as ComponentKind[];
+ kinds.sort();
+
+ const onSelect = (kind: string) => {
+ studioOps.updateProps({ kind });
+ };
+
+ const onMouseMove = (e: React.MouseEvent) => {
+ setMousePos({ x: e.clientX + 20, y: e.clientY + 20 });
+ };
+
+ const Preview = hoveredKind ? variants[hoveredKind] : null;
+ const PREVIEW_WIDTH = 600;
+ const PREVIEW_HEIGHT = 200;
+
+ const clampedX = Math.min(
+ mousePos.x,
+ window.innerWidth - PREVIEW_WIDTH - 90
+ ); // 90px buffer
+
+ return (
+