diff --git a/apps/next/app/features/code-generator/CodeGenerator.tsx b/apps/next/app/features/code-generator/CodeGenerator.tsx index 43d9847..7146eb9 100644 --- a/apps/next/app/features/code-generator/CodeGenerator.tsx +++ b/apps/next/app/features/code-generator/CodeGenerator.tsx @@ -1,14 +1,24 @@ "use client"; -import React from "react"; +import React, { useEffect } from "react"; import { Button } from "@ui/button"; import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; import { useSavedFunctions } from "@/contexts/SavedFunctionsContext"; import { SortableBlockList } from "./SortableBlockList"; import { SaveDialog } from "./Dialogs"; import { CodeViewer } from "@/components/CodeViewer"; +import { BlockEditor } from "@/components/editor"; +import { useBlockEditor } from "@/contexts/BlockEditorContext"; +import { CodebaseInfo } from "@ozhanefe/ts-codegenerator"; -export const CodeGenerator: React.FC = () => { - const { state, setState, code } = useCodeGenerator(); +interface CodeGeneratorProps { + codebaseInfo: CodebaseInfo; +} + +export const CodeGenerator: React.FC = ({ + codebaseInfo, +}) => { + const { state, setState, code, setCodebaseInfo } = useCodeGenerator(); + const { setCurrentBlock } = useBlockEditor(); const { saveCurrentState } = useSavedFunctions(); const isEmpty = state.blocks.length === 0; @@ -24,10 +34,18 @@ export const CodeGenerator: React.FC = () => { variables: [], isAsync: false, }); + setCurrentBlock(null); }; + useEffect(() => { + setCodebaseInfo(codebaseInfo); + }, [codebaseInfo]); + return (
+
+ +
diff --git a/apps/next/app/features/code-generator/SortableBlockList.tsx b/apps/next/app/features/code-generator/SortableBlockList.tsx index d4b345f..a265827 100644 --- a/apps/next/app/features/code-generator/SortableBlockList.tsx +++ b/apps/next/app/features/code-generator/SortableBlockList.tsx @@ -22,6 +22,7 @@ import { import { CSS } from "@dnd-kit/utilities"; import { BlockViewRenderer } from "@/components/blocks"; import { Cross1Icon } from "@radix-ui/react-icons"; +import { useBlockEditor } from "@/contexts/BlockEditorContext"; type SortableItemProps = { block: CodeBlock; @@ -36,6 +37,7 @@ const SortableItem: React.FC = ({ }) => { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: index, animateLayoutChanges: () => false }); + const { setCurrentBlock, currentBlock } = useBlockEditor(); const style = { transform: CSS.Transform.toString(transform), @@ -48,6 +50,10 @@ const SortableItem: React.FC = ({ ) => { event.preventDefault(); onRemove(index); + + if (currentBlock?.index === block.index) { + setCurrentBlock(null); + } }; return ( diff --git a/apps/next/app/features/code-generator/page.tsx b/apps/next/app/features/code-generator/page.tsx index 4c2cef1..9f8831f 100644 --- a/apps/next/app/features/code-generator/page.tsx +++ b/apps/next/app/features/code-generator/page.tsx @@ -12,11 +12,11 @@ export default function CodeGeneratorPage() {

Code Generator

- +
- +
); } diff --git a/apps/next/components/Providers.tsx b/apps/next/components/Providers.tsx index 17f61e6..db27eb8 100644 --- a/apps/next/components/Providers.tsx +++ b/apps/next/components/Providers.tsx @@ -2,6 +2,7 @@ import { DndContext } from "@dnd-kit/core"; import { CodeGeneratorProvider } from "@/contexts/CodeGeneratorContext"; import { SavedFunctionsProvider } from "@/contexts/SavedFunctionsContext"; +import { BlockEditorProvider } from "@/contexts/BlockEditorContext"; export const Providers: React.FC> = ({ children, @@ -9,7 +10,9 @@ export const Providers: React.FC> = ({ return ( - {children} + + {children} + ); diff --git a/apps/next/components/SearchDialog.tsx b/apps/next/components/SearchDialog.tsx index 04c3a25..13911ab 100644 --- a/apps/next/components/SearchDialog.tsx +++ b/apps/next/components/SearchDialog.tsx @@ -17,15 +17,11 @@ import { import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; import { KeyCombinationLabel } from "@ui/key-combination-label"; -interface SearchDialogProps { - codebaseInfo: CodebaseInfo; -} - -export const SearchDialog: React.FC = ({ codebaseInfo }) => { +export const SearchDialog: React.FC = () => { const [open, setOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); - const { state, setState } = useCodeGenerator(); + const { state, setState, codebaseInfo } = useCodeGenerator(); const addFunction = useCallback( (func: FunctionInfo) => { @@ -62,6 +58,8 @@ export const SearchDialog: React.FC = ({ codebaseInfo }) => { }, [open, searchResults, state]); useEffect(() => { + if (!codebaseInfo) return; + if (searchQuery === "") { setSearchResults([]); } else { diff --git a/apps/next/components/blocks/BlockContainer.tsx b/apps/next/components/blocks/BlockContainer.tsx index a3e7c6d..8eed64f 100644 --- a/apps/next/components/blocks/BlockContainer.tsx +++ b/apps/next/components/blocks/BlockContainer.tsx @@ -1,10 +1,11 @@ "use client"; -import { CodeBlock } from '@ozhanefe/ts-codegenerator'; -import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons'; -import React from 'react'; -import { IfBlockView } from './IfBlockView'; -import { WhileBlockView } from './WhileBlockView'; -import { FunctionCallBlockView } from './FunctionCallBlockView'; +import { CodeBlock } from "@ozhanefe/ts-codegenerator"; +import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons"; +import React, { useCallback } from "react"; +import { IfBlockView } from "./IfBlockView"; +import { WhileBlockView } from "./WhileBlockView"; +import { FunctionCallBlockView } from "./FunctionCallBlockView"; +import { useBlockEditor } from "@/contexts/BlockEditorContext"; interface BlockContainerProps { title: string; @@ -17,65 +18,95 @@ interface BlockViewRendererProps { block: CodeBlock; } -export const BlockViewRenderer: React.FC = React.memo(({ block }) => { - switch (block.blockType) { - case 'if': - return ( - )} - elseIfBlocks={block.elseIfBlocks?.map((elseIf) => ({ - condition: elseIf.condition, - blocks: elseIf.blocks.map((b, j) => ) - }))} - elseBlock={block.elseBlock && { - blocks: block.elseBlock.blocks.map((b, i) => ) - }} - /> - ); - case 'while': - return ( - - {block.loopBlocks.map((b, i) => )} - - ); - case 'functionCall': - return ; - default: - return null; - } -}); +export const BlockViewRenderer: React.FC = React.memo( + ({ block }) => { + const { setCurrentBlock } = useBlockEditor(); + const handleClick = useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + setCurrentBlock(block); + }, + [block, setCurrentBlock] + ); -export const BlockContainer: React.FC = ({ title, type, children, isCollapsible = false }) => { + return ( +
+ {block.blockType === "if" && ( + ( + + ))} + elseIfBlocks={block.elseIfBlocks?.map((elseIf) => ({ + condition: elseIf.condition, + blocks: elseIf.blocks.map((b, j) => ( + + )), + }))} + elseBlock={ + block.elseBlock && { + blocks: block.elseBlock.blocks.map((b, i) => ( + + )), + } + } + /> + )} + {block.blockType === "while" && ( + + {block.loopBlocks.map((b, i) => ( + + ))} + + )} + {block.blockType === "functionCall" && ( + + )} +
+ ); + } +); + +export const BlockContainer: React.FC = ({ + title, + type, + children, + isCollapsible = false, +}) => { const [isOpen, setIsOpen] = React.useState(true); const blockColors = { - functionCall: 'bg-blue-100 border-blue-200', - if: 'bg-green-100 border-green-200', - elseif: 'bg-yellow-100 border-yellow-200', - else: 'bg-orange-100 border-orange-200', - while: 'bg-purple-100 border-purple-200', - default: 'bg-gray-100 border-gray-200' + functionCall: "bg-blue-100 border-blue-200", + if: "bg-green-100 border-green-200", + elseif: "bg-yellow-100 border-yellow-200", + else: "bg-orange-100 border-orange-200", + while: "bg-purple-100 border-purple-200", + default: "bg-gray-100 border-gray-200", }; const color = blockColors[type] || blockColors.default; + const handleCollapse = (event: React.MouseEvent) => { + event.preventDefault(); + setIsOpen(!isOpen); + }; + return (
{title} {isCollapsible && ( - )}
- {(!isCollapsible || isOpen) && ( -
- {children} -
- )} + {(!isCollapsible || isOpen) &&
{children}
}
); -}; \ No newline at end of file +}; diff --git a/apps/next/components/editor/BlockEditor.tsx b/apps/next/components/editor/BlockEditor.tsx new file mode 100644 index 0000000..a52ec2b --- /dev/null +++ b/apps/next/components/editor/BlockEditor.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { useBlockEditor } from "@/contexts/BlockEditorContext"; +import { FunctionEditor } from "./FunctionEditor"; +import { IfEditor } from "./IfEditor"; +import { WhileEditor } from "./WhileEditor"; +import { BlockAdder } from "./components/BlockAdder"; +import { Button } from "@ui/button"; +import { XCircleIcon } from "lucide-react"; + +export const BlockEditor = () => { + const { currentBlock, setCurrentBlock } = useBlockEditor(); + + if (!currentBlock) return ; + + return ( +
+ {currentBlock.blockType === "functionCall" && ( + + )} + {currentBlock.blockType === "if" && } + {currentBlock.blockType === "while" && ( + + )} + +
+ ); +}; + +const EmptyBlockEditor = () => { + return ( +
+ +
+ ); +}; diff --git a/apps/next/components/editor/FunctionEditor.tsx b/apps/next/components/editor/FunctionEditor.tsx new file mode 100644 index 0000000..4cf66aa --- /dev/null +++ b/apps/next/components/editor/FunctionEditor.tsx @@ -0,0 +1,82 @@ +import { FunctionCallBlock, FunctionInfo } from "@ozhanefe/ts-codegenerator"; +import React, { useState } from "react"; +import { FunctionCombobox } from "./components/FunctionCombobox"; +import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; +import { Input } from "@ui/input"; +import { Button } from "@ui/button"; +import { Separator } from "@ui/separator"; + +export const FunctionEditor: React.FC<{ block: FunctionCallBlock }> = ({ + block, +}) => { + const { setState } = useCodeGenerator(); + const [variableName, setVariableName] = useState( + block.returnVariable?.name + ); + const { functionInfo } = block; + + const handleSelect = (selectedFunction: FunctionInfo) => { + setState((state) => { + if (state.blocks.map((b) => b.index).includes(block.index)) { + const blocks = state.blocks.map((b) => { + if (b.index === block.index) { + return { + ...b, + returnVariable: { + name: variableName, + type: selectedFunction.returnType, + }, + functionInfo: selectedFunction, + }; + } + return b; + }); + return { + ...state, + blocks, + }; + } + return state; + }); + }; + + const handleRename = () => { + setState((state) => { + if (state.blocks.map((b) => b.index).includes(block.index)) { + const blocks = state.blocks.map((b) => { + if (b.index === block.index) { + return { + ...b, + returnVariable: { + name: variableName, + type: functionInfo.returnType, + }, + variableName: variableName, + }; + } + return b; + }); + return { + ...state, + blocks, + }; + } + return state; + }); + }; + + return ( + + + +
+ setVariableName(e.target.value)} + /> + +
+ +
+ ); +}; diff --git a/apps/next/components/editor/IfEditor.tsx b/apps/next/components/editor/IfEditor.tsx new file mode 100644 index 0000000..f410bfd --- /dev/null +++ b/apps/next/components/editor/IfEditor.tsx @@ -0,0 +1,40 @@ +import React, { useEffect, useState } from "react"; +import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; +import { IfBlock, findAndUpdateBlock } from "@ozhanefe/ts-codegenerator"; +import { Input } from "@ui/input"; +import { Separator } from "@ui/separator"; +import { BlockAdder } from "./components/BlockAdder"; + +export const IfEditor: React.FC<{ block: IfBlock }> = ({ block }) => { + const { setState } = useCodeGenerator(); + const [conditionInput, setConditionInput] = useState(block.condition); + + useEffect(() => { + setConditionInput(block.condition); + }, [block.condition]); + + const handleConditionChange = ( + event: React.ChangeEvent + ) => { + setState((state) => ({ + ...state, + blocks: findAndUpdateBlock(state.blocks, block.index, (b) => ({ + ...b, + condition: event.target.value, + })), + })); + + setConditionInput(event.target.value); + }; + + return ( + + + +
+ +
+ +
+ ); +}; diff --git a/apps/next/components/editor/WhileEditor.tsx b/apps/next/components/editor/WhileEditor.tsx new file mode 100644 index 0000000..3616737 --- /dev/null +++ b/apps/next/components/editor/WhileEditor.tsx @@ -0,0 +1,52 @@ +import React, { useEffect, useState } from "react"; +import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; +import { WhileLoopBlock } from "@ozhanefe/ts-codegenerator"; +import { Input } from "@ui/input"; +import { Separator } from "@ui/separator"; +import { BlockAdder } from "./components/BlockAdder"; + +export const WhileEditor: React.FC<{ block: WhileLoopBlock }> = ({ block }) => { + const { setState } = useCodeGenerator(); + const [conditionInput, setConditionInput] = useState(block.condition); + + useEffect(() => { + setConditionInput(block.condition); + }, [block.condition]); + + const handleConditionChange = ( + event: React.ChangeEvent + ) => { + setState((state) => { + if (state.blocks.map((b) => b.index).includes(block.index)) { + const blocks = state.blocks.map((b) => { + if (b.index === block.index) { + return { + ...b, + condition: event.target.value, + }; + } + return b; + }); + return { + ...state, + blocks, + }; + } + + return state; + }); + + setConditionInput(event.target.value); + }; + + return ( + + + +
+ +
+ +
+ ); +}; diff --git a/apps/next/components/editor/components/BlockAdder.tsx b/apps/next/components/editor/components/BlockAdder.tsx new file mode 100644 index 0000000..0496233 --- /dev/null +++ b/apps/next/components/editor/components/BlockAdder.tsx @@ -0,0 +1,196 @@ +import React, { useState, useCallback } from "react"; +import { Button } from "@ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { Input } from "@ui/input"; +import { PlusCircle } from "lucide-react"; +import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; +import { useBlockEditor } from "@/contexts/BlockEditorContext"; +import { + CodeBlock, + IfBlock, + WhileLoopBlock, + ElseIfBlock, + ElseBlock, + FunctionInfo, + findAndUpdateBlock, +} from "@ozhanefe/ts-codegenerator"; +import { FunctionCombobox } from "@/components/editor/components/FunctionCombobox"; + +type BlockType = CodeBlock["blockType"]; + +interface NestableBlockAdderProps { + parentBlock?: IfBlock | WhileLoopBlock; +} + +export const BlockAdder: React.FC = ({ + parentBlock, +}) => { + const [open, setOpen] = useState(false); + const [selectedType, setSelectedType] = useState(null); + const [condition, setCondition] = useState(""); + const { setState } = useCodeGenerator(); + const { setCurrentBlock, createFunctionInside } = useBlockEditor(); + + const createNewBlock = useCallback( + (type: BlockType, condition: string, index: number): CodeBlock => { + switch (type) { + case "if": + return { + blockType: "if", + condition, + thenBlocks: [], + elseIfBlocks: [], + index, + }; + case "while": + return { blockType: "while", condition, loopBlocks: [], index }; + case "else-if": + return { blockType: "else-if", condition, blocks: [], index }; + case "else": + return { blockType: "else", blocks: [], index }; + default: + throw new Error("Invalid block type"); + } + }, + [] + ); + + const handleAddBlock = useCallback( + (newBlock: CodeBlock) => { + setState((state) => { + const updateBlocks = (blocks: CodeBlock[]): CodeBlock[] => { + if (!parentBlock) return [...blocks, newBlock]; + + return findAndUpdateBlock( + blocks, + parentBlock.index, + (block): CodeBlock => { + if (block.blockType === "if") { + if (newBlock.blockType === "else-if") { + return { + ...block, + elseIfBlocks: [ + ...(block.elseIfBlocks || []), + newBlock as ElseIfBlock, + ], + }; + } else if (newBlock.blockType === "else") { + return { ...block, elseBlock: newBlock as ElseBlock }; + } else { + return { + ...block, + thenBlocks: [...block.thenBlocks, newBlock], + }; + } + } else if (block.blockType === "while") { + return { + ...block, + loopBlocks: [...block.loopBlocks, newBlock], + }; + } + return block; + } + ); + }; + + setCurrentBlock(newBlock); + setOpen(false); + setSelectedType(null); + setCondition(""); + + return { ...state, blocks: updateBlocks(state.blocks) }; + }); + }, + [parentBlock, setState, setCurrentBlock] + ); + + const handleAddControlBlock = useCallback(() => { + if (!selectedType || selectedType === "functionCall") return; + const newBlock = createNewBlock(selectedType, condition, -1); // Index will be set by state update + handleAddBlock(newBlock); + }, [selectedType, condition, createNewBlock, handleAddBlock]); + + const addFunction = (func: FunctionInfo) => { + createFunctionInside(func); + }; + + const canAddElseIf = parentBlock?.blockType === "if"; + const canAddElse = parentBlock?.blockType === "if" && !parentBlock.elseBlock; + + return ( +
+ + + + + + +
+ + +
+ {canAddElseIf && ( + + )} + {canAddElse && ( + + )} +
+
+ {selectedType && + selectedType !== "else" && + selectedType !== "functionCall" && ( + setCondition(e.target.value)} + className="flex-1" + /> + )} +
+ {selectedType && selectedType !== "functionCall" && ( + + )} +
+
+
+
+ ); +}; diff --git a/apps/next/components/editor/components/FunctionCombobox.tsx b/apps/next/components/editor/components/FunctionCombobox.tsx new file mode 100644 index 0000000..5adba41 --- /dev/null +++ b/apps/next/components/editor/components/FunctionCombobox.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useState } from "react"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { useCodeGenerator } from "@/contexts/CodeGeneratorContext"; +import { FunctionInfo } from "@ozhanefe/ts-codegenerator"; + +interface ComboboxDemoProps { + onSelect: (functionInfo: FunctionInfo) => void; + resetAfterSelect?: boolean; + popoverText?: string; +} + +export const FunctionCombobox: React.FC = ({ + onSelect, + resetAfterSelect, + popoverText = "Select a function", +}) => { + const { codebaseInfo } = useCodeGenerator(); + const functionInfos = codebaseInfo?.functions; + const [open, setOpen] = useState(false); + const [value, setValue] = useState(""); + + const handleSelect = (currentValue: string) => { + const functionInfo = functionInfos?.find( + (info) => info.name === currentValue + ); + if (functionInfo) { + setValue(currentValue); + setOpen(false); + onSelect(functionInfo); + } + + if (resetAfterSelect) { + setValue(""); + } + }; + + return ( + + + + + + + + Couldn't find it 😔 + + + {functionInfos?.map((info: FunctionInfo) => ( + handleSelect(info.name)} + > + + {info.name} + + ))} + + + + + + ); +}; diff --git a/apps/next/components/editor/index.ts b/apps/next/components/editor/index.ts new file mode 100644 index 0000000..e915f7b --- /dev/null +++ b/apps/next/components/editor/index.ts @@ -0,0 +1 @@ +export * from "./BlockEditor"; diff --git a/apps/next/contexts/BlockEditorContext.tsx b/apps/next/contexts/BlockEditorContext.tsx new file mode 100644 index 0000000..8148e2e --- /dev/null +++ b/apps/next/contexts/BlockEditorContext.tsx @@ -0,0 +1,85 @@ +import { + createContext, + Dispatch, + PropsWithChildren, + SetStateAction, + useContext, + useState, +} from "react"; +import { + CodeBlock, + FunctionInfo, + createFunctionCallBlock, +} from "@ozhanefe/ts-codegenerator"; +import { useCodeGenerator } from "./CodeGeneratorContext"; + +interface Context { + currentBlock: CodeBlock | null; + setCurrentBlock: Dispatch>; + createFunctionInside: (func: FunctionInfo) => void; +} + +const BlockEditorContext = createContext({ + currentBlock: null, + setCurrentBlock: () => {}, + createFunctionInside: () => {}, +}); + +export const BlockEditorProvider = ({ children }: PropsWithChildren<{}>) => { + const { setState } = useCodeGenerator(); + const [currentBlock, setCurrentBlock] = useState(null); + + const createFunctionInside = (func: FunctionInfo) => { + // add to root + if (!currentBlock) { + setState((state) => { + const { state: newState } = createFunctionCallBlock(func, state); + return newState; + }); + + return; + } + + switch (currentBlock.blockType) { + case "if": { + setState((state) => { + const { state: newState } = createFunctionCallBlock( + func, + state, + currentBlock + ); + return newState; + }); + + break; + } + + case "while": { + setState((state) => { + const { state: newState } = createFunctionCallBlock( + func, + state, + currentBlock + ); + return newState; + }); + } + } + }; + + return ( + + {children} + + ); +}; + +export const useBlockEditor = () => { + return useContext(BlockEditorContext); +}; diff --git a/apps/next/contexts/CodeGeneratorContext.tsx b/apps/next/contexts/CodeGeneratorContext.tsx index 3da6545..e2d50e5 100644 --- a/apps/next/contexts/CodeGeneratorContext.tsx +++ b/apps/next/contexts/CodeGeneratorContext.tsx @@ -1,4 +1,8 @@ -import { CodeGeneratorState, generateCode } from "@ozhanefe/ts-codegenerator"; +import { + CodebaseInfo, + CodeGeneratorState, + generateCode, +} from "@ozhanefe/ts-codegenerator"; import { createContext, Dispatch, @@ -12,6 +16,8 @@ import { interface Context { state: CodeGeneratorState; setState: Dispatch>; + codebaseInfo: CodebaseInfo | null; + setCodebaseInfo: Dispatch>; code: string; } @@ -22,6 +28,8 @@ const CodeGeneratorContext = createContext({ isAsync: false, }, setState: () => {}, + codebaseInfo: null, + setCodebaseInfo: () => {}, code: "", }); @@ -34,6 +42,7 @@ export const CodeGeneratorProvider: React.FC = ({ isAsync: false, }); const [code, setCode] = useState(""); + const [codebaseInfo, setCodebaseInfo] = useState(null); useEffect(() => { setCode(generateCode(state.blocks)); @@ -45,6 +54,8 @@ export const CodeGeneratorProvider: React.FC = ({ state, setState, code, + codebaseInfo, + setCodebaseInfo, }} > {children} diff --git a/apps/next/lib/utils.ts b/apps/next/lib/utils.ts index 361f510..c9edb03 100644 --- a/apps/next/lib/utils.ts +++ b/apps/next/lib/utils.ts @@ -56,7 +56,7 @@ export function generateRealisticDevDayState(): CodeGeneratorState { const ifTired: IfBlock = { condition: "energyLevel < 30", thenBlocks: [getCoffee], - elseBlock: { blocks: [writeCode] }, + elseBlock: { blocks: [writeCode], blockType: "else", index: 4 }, index: 3, blockType: "if", }; @@ -64,7 +64,7 @@ export function generateRealisticDevDayState(): CodeGeneratorState { const coding: WhileLoopBlock = { condition: "workHours < 8", loopBlocks: [ifTired], - index: 4, + index: 5, blockType: "while", }; @@ -72,7 +72,7 @@ export function generateRealisticDevDayState(): CodeGeneratorState { functionInfo: { name: "celebrate", returnType: "void" }, returnVariable: { name: "partyTime", type: "boolean" }, isAsync: false, - index: 5, + index: 6, blockType: "functionCall", }; @@ -80,15 +80,15 @@ export function generateRealisticDevDayState(): CodeGeneratorState { functionInfo: { name: "playVideoGames", returnType: "void" }, returnVariable: { name: "stressRelieved", type: "boolean" }, isAsync: false, - index: 6, + index: 7, blockType: "functionCall", }; const afterWorkMood: IfBlock = { condition: "linesOfCode > 100", thenBlocks: [celebrate], - elseBlock: { blocks: [playVideoGames] }, - index: 7, + elseBlock: { blocks: [playVideoGames], blockType: "else", index: 9 }, + index: 8, blockType: "if", }; @@ -97,13 +97,13 @@ export function generateRealisticDevDayState(): CodeGeneratorState { return { blocks, variables: [ - { name: "awake", type: "boolean", index: 0 }, - { name: "energyLevel", type: "number", index: 1 }, - { name: "workHours", type: "number", index: 2 }, - { name: "linesOfCode", type: "number", index: 3 }, - { name: "caffeinated", type: "boolean", index: 4 }, - { name: "partyTime", type: "boolean", index: 5 }, - { name: "stressRelieved", type: "boolean", index: 6 }, + { index: 0, name: "awake", type: "boolean" }, + { index: 1, name: "caffeinated", type: "boolean" }, + { index: 2, name: "energyLevel", type: "number" }, + { index: 3, name: "workHours", type: "number" }, + { index: 4, name: "linesOfCode", type: "number" }, + { index: 5, name: "partyTime", type: "boolean" }, + { index: 6, name: "stressRelieved", type: "boolean" }, ], isAsync: false, }; diff --git a/package.json b/package.json index 6d8b372..0ec456c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@repo/eslint-config": "*", "@repo/typescript-config": "*", - "@ozhanefe/ts-codegenerator": "^2.2.0", + "@ozhanefe/ts-codegenerator": "^2.6.1", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", "@types/jest": "^29.5.12", @@ -36,5 +36,8 @@ "workspaces": [ "apps/*", "packages/*" - ] + ], + "dependencies": { + "lucide-react": "^0.424.0" + } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 01b8494..e322e06 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -26,9 +26,12 @@ }, "dependencies": { "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-separator": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "cmdk": "^1.0.0", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7" } diff --git a/packages/ui/src/command.tsx b/packages/ui/src/command.tsx new file mode 100644 index 0000000..fa67192 --- /dev/null +++ b/packages/ui/src/command.tsx @@ -0,0 +1,155 @@ +"use client"; + +import * as React from "react"; +import { type DialogProps } from "@radix-ui/react-dialog"; +import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; +import { Command as CommandPrimitive } from "cmdk"; + +import { cn } from "@ui/utils/tailwind-utils"; +import { Dialog, DialogContent } from "@ui/dialog"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/packages/ui/src/label.tsx b/packages/ui/src/label.tsx new file mode 100644 index 0000000..395409e --- /dev/null +++ b/packages/ui/src/label.tsx @@ -0,0 +1,26 @@ +"use client"; + +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@ui/utils/tailwind-utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/packages/ui/src/popover.tsx b/packages/ui/src/popover.tsx index f48e9d4..4439f12 100644 --- a/packages/ui/src/popover.tsx +++ b/packages/ui/src/popover.tsx @@ -1,15 +1,15 @@ -"use client"; +"use client" -import * as React from "react"; -import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" -import { cn } from "@ui/utils/tailwind-utils"; +import { cn } from "@ui/utils/tailwind-utils" -const Popover = PopoverPrimitive.Root; +const Popover = PopoverPrimitive.Root -const PopoverTrigger = PopoverPrimitive.Trigger; +const PopoverTrigger = PopoverPrimitive.Trigger -const PopoverAnchor = PopoverPrimitive.Anchor; +const PopoverAnchor = PopoverPrimitive.Anchor const PopoverContent = React.forwardRef< React.ElementRef, @@ -27,7 +27,7 @@ const PopoverContent = React.forwardRef< {...props} /> -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }