Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion starters/cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"typecheck": "tsc"
},
"dependencies": {
"framer-plugin": "^3.1.0",
"framer-plugin": "^3.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
31 changes: 20 additions & 11 deletions starters/cms/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,17 @@ form {
top: 0;
}

.field-input {
select {
width: 100%;
}

select:disabled,
input[type="text"]:disabled,
.ignored {
opacity: 0.5;
}

.mapping select {
flex-shrink: 1;
}

Expand Down Expand Up @@ -88,10 +97,6 @@ form {
color: var(--framer-color-text-secondary);
}

.setup select {
width: 100%;
}

.mapping {
padding-bottom: 0;
}
Expand Down Expand Up @@ -130,10 +135,6 @@ form {
gap: 8px;
}

.mapping .source-field[aria-disabled="true"] {
opacity: 0.5;
}

.mapping .source-field:focus-visible {
outline: none;
box-shadow: inset 0 0 0 1px var(--framer-color-tint);
Expand All @@ -147,14 +148,18 @@ form {
box-shadow: none;
}

[data-framer-theme=light] .mapping .source-field input[type="checkbox"]:not(:checked) {
[data-framer-theme="light"] .mapping .source-field input[type="checkbox"]:not(:checked) {
background: #ccc;
}

[data-framer-theme=dark] .mapping .source-field input[type="checkbox"]:not(:checked) {
[data-framer-theme="dark"] .mapping .source-field input[type="checkbox"]:not(:checked) {
background: #666;
}

.mapping input[type="text"] {
width: 100%;
}

.mapping footer {
position: sticky;
bottom: 0;
Expand All @@ -178,3 +183,7 @@ form {
background: linear-gradient(to bottom, transparent, var(--framer-color-bg));
pointer-events: none;
}

.mapping footer > button {
text-transform: capitalize;
}
48 changes: 23 additions & 25 deletions starters/cms/src/FieldMapping.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
import { type EditableManagedCollectionField, framer, type ManagedCollection } from "framer-plugin"
import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin"
import { useEffect, useState } from "react"
import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data"

interface FieldMappingRowProps {
field: EditableManagedCollectionField
field: ManagedCollectionFieldInput
originalFieldName: string | undefined
disabled: boolean
isIgnored: boolean
onToggleDisabled: (fieldId: string) => void
onNameChange: (fieldId: string, name: string) => void
}

function FieldMappingRow({ field, originalFieldName, disabled, onToggleDisabled, onNameChange }: FieldMappingRowProps) {
function FieldMappingRow({
field,
originalFieldName,
isIgnored,
onToggleDisabled,
onNameChange,
}: FieldMappingRowProps) {
return (
<>
<button
type="button"
className="source-field"
aria-disabled={disabled}
className={`source-field ${isIgnored ? "ignored" : ""}`}
onClick={() => onToggleDisabled(field.id)}
tabIndex={0}
>
<input type="checkbox" checked={!disabled} tabIndex={-1} readOnly />
<input type="checkbox" checked={!isIgnored} tabIndex={-1} readOnly />
<span>{originalFieldName ?? field.id}</span>
</button>
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" fill="none">
<title>maps to</title>
<path
fill="transparent"
stroke="#999"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="m2.5 7 3-3-3-3"
/>
</svg>
<input
type="text"
style={{ width: "100%", opacity: disabled ? 0.5 : 1 }}
disabled={disabled}
disabled={isIgnored}
placeholder={field.id}
value={field.name}
onChange={event => onNameChange(field.id, event.target.value)}
Expand All @@ -50,7 +54,7 @@ function FieldMappingRow({ field, originalFieldName, disabled, onToggleDisabled,
)
}

const initialManagedCollectionFields: EditableManagedCollectionField[] = []
const initialManagedCollectionFields: ManagedCollectionFieldInput[] = []
const initialFieldIds: ReadonlySet<string> = new Set()

interface FieldMappingProps {
Expand All @@ -68,7 +72,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie

const [possibleSlugFields] = useState(() => dataSource.fields.filter(field => field.type === "string"))

const [selectedSlugField, setSelectedSlugField] = useState<EditableManagedCollectionField | null>(
const [selectedSlugField, setSelectedSlugField] = useState<ManagedCollectionFieldInput | null>(
possibleSlugFields.find(field => field.id === initialSlugFieldId) ?? possibleSlugFields[0] ?? null
)

Expand Down Expand Up @@ -204,23 +208,17 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
key={`field-${field.id}`}
field={field}
originalFieldName={dataSource.fields.find(sourceField => sourceField.id === field.id)?.name}
disabled={ignoredFieldIds.has(field.id)}
isIgnored={ignoredFieldIds.has(field.id)}
onToggleDisabled={toggleFieldDisabledState}
onNameChange={changeFieldName}
/>
))}
</div>

<footer>
<hr className="sticky-top" />
<button disabled={isSyncing} tabIndex={0}>
{isSyncing ? (
<div className="framer-spinner" />
) : (
<span>
Import <span style={{ textTransform: "capitalize" }}>{dataSourceName}</span>
</span>
)}
<hr />
<button type="submit" disabled={isSyncing}>
{isSyncing ? <div className="framer-spinner" /> : `Import ${dataSourceName}`}
</button>
</footer>
</form>
Expand Down
3 changes: 2 additions & 1 deletion starters/cms/src/SelectDataSource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function SelectDataSource({ onSelectDataSource }: SelectDataSourceProps)
<div className="intro">
<div className="logo">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="none">
<title>CMS Starter</title>
<path
fill="currentColor"
d="M15.5 8c3.59 0 6.5 1.38 6.5 3.083 0 1.702-2.91 3.082-6.5 3.082S9 12.785 9 11.083C9 9.38 11.91 8 15.5 8Zm6.5 7.398c0 1.703-2.91 3.083-6.5 3.083S9 17.101 9 15.398v-2.466c0 1.703 2.91 3.083 6.5 3.083s6.5-1.38 6.5-3.083Zm0 4.316c0 1.703-2.91 3.083-6.5 3.083S9 21.417 9 19.714v-2.466c0 1.702 2.91 3.083 6.5 3.083S22 18.95 22 17.248Z"
Expand Down Expand Up @@ -62,7 +63,7 @@ export function SelectDataSource({ onSelectDataSource }: SelectDataSourceProps)
))}
</select>
</label>
<button disabled={!selectedDataSourceId || isLoading}>
<button type="submit" disabled={!selectedDataSourceId || isLoading}>
{isLoading ? <div className="framer-spinner" /> : "Next"}
</button>
</form>
Expand Down
16 changes: 8 additions & 8 deletions starters/cms/src/data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
type EditableManagedCollectionField,
type ManagedCollectionFieldInput,
type FieldDataInput,
framer,
type ManagedCollection,
Expand All @@ -13,7 +13,7 @@ export const PLUGIN_KEYS = {

export interface DataSource {
id: string
fields: readonly EditableManagedCollectionField[]
fields: readonly ManagedCollectionFieldInput[]
items: FieldDataInput[]
}

Expand Down Expand Up @@ -44,7 +44,7 @@ export async function getDataSource(dataSourceId: string, abortSignal?: AbortSig
const dataSource = await dataSourceResponse.json()

// Map your source fields to supported field types in Framer
const fields: EditableManagedCollectionField[] = []
const fields: ManagedCollectionFieldInput[] = []
for (const field of dataSource.fields) {
switch (field.type) {
case "string":
Expand Down Expand Up @@ -83,9 +83,9 @@ export async function getDataSource(dataSourceId: string, abortSignal?: AbortSig
}

export function mergeFieldsWithExistingFields(
sourceFields: readonly EditableManagedCollectionField[],
existingFields: readonly EditableManagedCollectionField[]
): EditableManagedCollectionField[] {
sourceFields: readonly ManagedCollectionFieldInput[],
existingFields: readonly ManagedCollectionFieldInput[]
): ManagedCollectionFieldInput[] {
return sourceFields.map(sourceField => {
const existingField = existingFields.find(existingField => existingField.id === sourceField.id)
if (existingField) {
Expand All @@ -98,8 +98,8 @@ export function mergeFieldsWithExistingFields(
export async function syncCollection(
collection: ManagedCollection,
dataSource: DataSource,
fields: readonly EditableManagedCollectionField[],
slugField: EditableManagedCollectionField
fields: readonly ManagedCollectionFieldInput[],
slugField: ManagedCollectionFieldInput
) {
const sanitizedFields = fields.map(field => ({
...field,
Expand Down