Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Use any lucide icon for the add node button #64

Merged
merged 13 commits into from
Oct 4, 2024
23 changes: 21 additions & 2 deletions components/interview/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import AddAPersonIcon from './icons/add-a-person-svg-react';
import MenuNewSessionIcon from './icons/menu-new-session.svg.react';
import AddNode from './icons/LucideAddNode';
import type { NodeIcon } from '~/schemas/protocol/codebook/entities';

export default function ActionButton({ onClick }: { onClick: () => void }) {
const renderIcon = (icon: NodeIcon) => {
switch (icon) {
case 'add-a-person':
return <AddAPersonIcon />;
case 'add-a-place':
return <div>Place</div>; // TODO: implement add-a-place icon
default:
return <AddNode iconName={icon} />;
}
};
jthrilly marked this conversation as resolved.
Show resolved Hide resolved

export default function ActionButton({
onClick,
icon,
}: {
onClick: () => void;
jthrilly marked this conversation as resolved.
Show resolved Hide resolved
icon: NodeIcon;
}) {
return (
<button onClick={onClick} className="relative flex h-40 w-40">
<div className="absolute inset-0 flex items-center justify-center rounded-full">
<AddAPersonIcon />
{renderIcon(icon)}
</div>
<div className="absolute right-0 top-3 flex h-16 w-16 items-center justify-center rounded-full bg-muted p-5">
<MenuNewSessionIcon />
Expand Down
42 changes: 42 additions & 0 deletions components/interview/icons/LucideAddNode.tsx
jthrilly marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { cn } from '~/lib/utils';

import React, { lazy, Suspense } from 'react';
import type { LucideProps } from 'lucide-react';
import dynamicIconImports from 'lucide-react/dynamicIconImports';
import { Circle } from 'lucide-react';

// do we need a fallback for this?
jthrilly marked this conversation as resolved.
Show resolved Hide resolved
const fallback = <Circle size={60} />;

type IconProps = {
name: keyof typeof dynamicIconImports;
} & Omit<LucideProps, 'ref'>;

const Icon = ({ name, ...props }: IconProps) => {
const LucideIcon = lazy(dynamicIconImports[name]);

return (
<Suspense fallback={fallback}>
<LucideIcon {...props} />
</Suspense>
);
};

export default function LucideAddNode({
iconName,
}: {
iconName: keyof typeof dynamicIconImports;
}) {
return (
<div
className={cn(
'inline-flex items-center justify-center rounded-full bg-node-1 text-node-1-foreground',
'bg-[repeating-linear-gradient(145deg,transparent,transparent_50%,rgba(0,0,0,0.1)_50%,rgba(0,0,0,0.1)_100%)]',
'h-36 w-36',
)}
aria-label="Add Node"
>
<Icon name={iconName} size={60} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { cn } from '~/lib/utils';
import { interfaceWrapperClasses } from '../../ui/SimpleShell';
import { withOnboardingWizard } from '~/components/onboard-wizard/withOnboardingWizard';
import { type InterviewStage } from '../../ui/InterviewShell';
import { type TNodeType } from '~/schemas/protocol/codebook/entities';

const demoPrompts = [
{
Expand Down Expand Up @@ -75,6 +76,11 @@ const demoNodes = [
},
];

const demoNodeType = {
color: 'seq-node-1',
icon: 'school',
} as TNodeType;

function NameGenerator(_props: InterviewStage) {
return (
<div className={cn(interfaceWrapperClasses, 'flex grow flex-col gap-4')}>
Expand All @@ -85,7 +91,7 @@ function NameGenerator(_props: InterviewStage) {
<NodeList items={demoNodes} />
</div>
</div>
<QuickNodeForm />
<QuickNodeForm nodeType={demoNodeType} />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import { useState } from 'react';
import ActionButton from '~/components/interview/ActionButton';
import Node from '~/components/Node';
import type { TNodeType } from '~/schemas/protocol/codebook/entities';

export default function QuickNodeForm() {
export default function QuickNodeForm({ nodeType }: { nodeType: TNodeType }) {
const [showForm, setShowForm] = useState(false);

return (
Expand All @@ -27,7 +28,10 @@ export default function QuickNodeForm() {
)}
{!showForm && (
<div className="mr-8" id="data-wizard-task-step-3">
<ActionButton onClick={() => setShowForm(true)} />
<ActionButton
onClick={() => setShowForm(true)}
icon={nodeType.icon}
/>
</div>
)}
</div>
Expand Down
17 changes: 16 additions & 1 deletion lib/db/sample-data/dev-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const devProtocol: Protocol = {
},
},
color: 'seq-node-2',
icon: 'Backpack', // example of using lucide icon
icon: 'backpack', // example of using lucide icon
},
},
},
Expand Down Expand Up @@ -126,6 +126,21 @@ export const devProtocol: Protocol = {
},
],
},
{
id: '2',
type: 'NameGenerator',
subject: {
entity: 'node',
id: 'school',
},
mode: 'quickAdd',
quickAddVariable: 'school',
prompts: [
{
id: '2',
},
],
},
],
};

Expand Down
8 changes: 6 additions & 2 deletions schemas/protocol/codebook/entities.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { z } from 'zod';
import { VariableDefinitionSchema } from './variables';
import * as lucide from 'lucide-react';
import dynamicIconImports from 'lucide-react/dynamicIconImports';

const lucideIcons = Object.keys(lucide) as (keyof typeof lucide)[];
const lucideIcons = Object.keys(
dynamicIconImports,
) as (keyof typeof dynamicIconImports)[];
jthrilly marked this conversation as resolved.
Show resolved Hide resolved

export const NodeColors = [
'seq-node-1',
Expand All @@ -21,6 +23,8 @@ export const NodeIcons = [
...lucideIcons,
] as const;
jthrilly marked this conversation as resolved.
Show resolved Hide resolved

export type NodeIcon = (typeof NodeIcons)[number];

export const EdgeColors = [
'seq-edge-1',
'seq-edge-2',
Expand Down