Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a07ab07
Add plasmic code component integration to component library
Ngalstyan4 Jun 10, 2025
bc84151
Update plasmic init client
Ngalstyan4 Jun 11, 2025
e57cd44
Add plasmic CLI
Ngalstyan4 Jun 12, 2025
d675fda
Add notes to the readme on plasmic integration
Ngalstyan4 Jun 12, 2025
fb3a930
Add plasmic watch script
Ngalstyan4 Jun 12, 2025
36471fd
Commit generated showcase root component file
Ngalstyan4 Jun 12, 2025
8c36b6e
Move back to hardcoding token and project id
Ngalstyan4 Jun 12, 2025
56553c4
Register some component library components
Ngalstyan4 Jun 12, 2025
64fddde
readme
Ngalstyan4 Jun 12, 2025
a673e1b
Use the new testimonial
Ngalstyan4 Jun 12, 2025
6d82008
Add plasmic dependencies
Ngalstyan4 Jun 18, 2025
9c4af1d
Add support libraries
Ngalstyan4 Jun 25, 2025
5f6272b
Modify testimonial components for integration
Ngalstyan4 Jun 25, 2025
0f31e6d
ignore autogen components
Ngalstyan4 Jun 26, 2025
e0eea18
Remove kinds from TestimonialProps
Ngalstyan4 Jun 26, 2025
eb87587
Ignore aider files
Ngalstyan4 Jun 26, 2025
af925f4
Consolidate plasmic code related to testimonials to a single file
Ngalstyan4 Jun 26, 2025
de21454
Downgrade to react 18
Ngalstyan4 Jun 26, 2025
ddb2661
Add an animated testimonial component and support for different edito…
Ngalstyan4 Jun 26, 2025
469e28e
Testimonial plasmic integration fixes
Ngalstyan4 Jun 26, 2025
9b52931
reveal heading typecheck
Ngalstyan4 Jun 26, 2025
fce98cf
Use a heuristic to handle encoded strings in html
Ngalstyan4 Jun 26, 2025
ec39063
Rename plasmic registrations file for testimonials file
Ngalstyan4 Jun 26, 2025
1789331
Prepare to build a unified component for team members
Ngalstyan4 Jun 26, 2025
52adec8
Update paths for generated components
Ngalstyan4 Jun 26, 2025
5fcb2fb
Add LLM instructions for plasmic integration
Ngalstyan4 Jun 27, 2025
979e30a
Add initial version of team member plasmic integration
Ngalstyan4 Jun 27, 2025
05bdb3d
Remove unnecessary console log
Ngalstyan4 Jun 27, 2025
9050c75
Update generation instructions for plasmic integrations
Ngalstyan4 Jun 27, 2025
c9e21d5
Remove team member plasmic integration to redo the generation
Ngalstyan4 Jun 27, 2025
260d99c
Add more team member integrations
Ngalstyan4 Jun 27, 2025
1c95a6a
Fix team member components
Ngalstyan4 Jun 27, 2025
fa77969
Update generation instructions
Ngalstyan4 Jun 27, 2025
221dcad
feat: add Plasmic integration for team-member-04 component
Ngalstyan4 Jun 27, 2025
d56b395
Fix team member 4 after claude codeup
Ngalstyan4 Jun 27, 2025
958b6fb
feat: add team-member-06 component with Plasmic integration
Ngalstyan4 Jun 27, 2025
b282a56
Prepare pricing card plasmic integrations
Ngalstyan4 Jun 27, 2025
69ab99b
feat: integrate pricing card component with Plasmic and add dynamic c…
Ngalstyan4 Jun 27, 2025
495e1f4
feat: add LinkButton Plasmic component registration wrapper
Ngalstyan4 Jun 27, 2025
ec3b809
Fix pricing card
Ngalstyan4 Jun 27, 2025
d440c35
make "kind" selection generic for editor
var77 Jul 10, 2025
1ea4007
add faq-01 component
var77 Jul 11, 2025
79c140e
add faq-02 component
var77 Jul 11, 2025
cfb9214
add faq-03
var77 Jul 11, 2025
ebcdba0
refactor code for faq items
var77 Jul 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
.aider*
components-autogen
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<CircleIcon color="red" />

## TODO

Coming up:
Expand Down
4 changes: 2 additions & 2 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
];
Expand Down
117 changes: 117 additions & 0 deletions lib/plasmic-component-registrations/GENERATION_INSTRUCTIONS.md
Original file line number Diff line number Diff line change
@@ -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) && (
<span className="text-muted-foreground text-sm md:text-base truncate">
- {[role, companyName].filter(Boolean).join(", ")}
+ {role}
+ {role && companyName && ", "}
+ {companyName}
</span>
)}
```

### 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 (
<figure
className={cn(
@@ -54,13 +44,7 @@ export function Testimonial07({
{/* Attribution Row */}
<div className="flex flex-col md:flex-row items-center gap-[16px] md:gap-[20px] w-full mt-4 justify-center">
{/* Avatar */}
- <ProfileAvatar
- name={name}
- image={image}
- size="lg"
- fallbackType="initials"
- className="size-14"
- />
+ {avatar}
{/* Name/Role/Company */}
<div className="flex flex-col min-w-0 items-center md:items-start">
<span className="font-semibold text-base truncate">{name}</span>
@@ -73,12 +57,16 @@ export function Testimonial07({
{/* Company Logo */}
{companyLogo && (
<div className="flex items-center min-h-[56px] md:pl-8 md:border-l md:border-muted">
- <img
- src={companyLogo}
- alt={companyName ? `${companyName} logo` : "Company logo"}
- className="h-10 w-auto object-contain"
- aria-hidden={companyName ? "false" : "true"}
- />
+ {typeof companyLogo == "string" ? (
+ <img
+ src={companyLogo}
+ alt={companyName ? `${companyName} logo` : "Company logo"}
+ className="h-10 w-auto object-contain"
+ aria-hidden={companyName ? "false" : "true"}
+ />
+ ) : (
+ companyLogo
+ )}
</div>
)}
</div>
```

### snippet 3

```diff
-
-export interface Testimonial09Props
- extends React.HTMLAttributes<HTMLDivElement> {
- 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 <p> 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 <p> to <div> or <span>. <p> 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, )
135 changes: 135 additions & 0 deletions lib/plasmic-component-registrations/component-with-variants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
ActionProps,
usePlasmicCanvasComponentInfo,
} from "@plasmicapp/react-web/lib/host";
import { useRef, useState } from "react";

export type ComponentMap = Record<string, React.FC<any>>;

export function getComponentAndControls<ComponentProps>(
variants: ComponentMap,
defaultVariant = "01"
) {
type ComponentKind = keyof typeof variants;
function Component<K extends ComponentKind>({
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<ComponentProps & { kind: ComponentKind }>
> = ({ studioOps, componentProps }) => {
const [hoveredKind, setHoveredKind] = useState<ComponentKind | null>(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 (
<div
ref={containerRef}
style={{
display: "block",
gap: "8px",
alignItems: "center",
position: "relative",
padding: "8px 0",
// fontFamily: "sans-serif",
}}
onMouseMove={onMouseMove}
>
{kinds.map((kind) => (
<button
key={kind}
onClick={() => onSelect(kind)}
onMouseEnter={() => setHoveredKind(kind)}
onMouseLeave={() => setHoveredKind(null)}
style={{
padding: "6px 10px",
borderRadius: "4px",
border:
componentProps.kind === kind
? "2px solid black"
: "1px solid #ccc",
backgroundColor:
componentProps.kind === kind ? "#f0f0f0" : "#fff",
cursor: "pointer",
transition: "all 0.2s",
}}
>
Kind {kind}
</button>
))}

{Preview && (
<div
style={{
position: "fixed",
top: mousePos.y,
left: clampedX,
zIndex: 1000,
pointerEvents: "none",
border: "1px solid #ccc",
background: "#fff",
padding: "8px",
borderRadius: "4px",
boxShadow: "0 2px 10px rgba(0,0,0,0.1)",
width: PREVIEW_WIDTH + "px",
height: PREVIEW_HEIGHT + "px",
}}
>
{/* tailwind currently does not work in preview since this runs in */}
{/* another iframe. */}
{/* TODO: fix tailwind here */}
<Preview {...componentProps} />
</div>
)}
</div>
);
};

const componentVariantProps = {
kind: {
type: "choice",
options: Object.keys(variants),
defaultValue: defaultVariant,
},
};

const componentVariantActions = [
{
type: "custom-action",
control: ComponentControl,
},
];
return {
Component,
componentVariantProps,
componentVariantActions,
};
}
Loading