diff --git a/js/react/lib/components/dropdown-tree/dropdown-tree.end.tsx b/js/react/lib/components/dropdown-tree/dropdown-tree.end.tsx new file mode 100644 index 0000000..7221981 --- /dev/null +++ b/js/react/lib/components/dropdown-tree/dropdown-tree.end.tsx @@ -0,0 +1,21 @@ +import { ArrowRight } from "@/icons"; +import { withAs } from "@/utils/hoc"; +import { PropsWithChildren } from "react"; + +type DropdownTreeEndProps = { + title: string; +} & PropsWithChildren; + +export const DropdownTreeEnd = withAs( + (Component, props: DropdownTreeEndProps) => { + const { title, children, ...rest } = props; + return ( + + + {title} + + {children} + + ); + } +); diff --git a/js/react/lib/components/dropdown-tree/dropdown-tree.start.tsx b/js/react/lib/components/dropdown-tree/dropdown-tree.start.tsx new file mode 100644 index 0000000..6e82336 --- /dev/null +++ b/js/react/lib/components/dropdown-tree/dropdown-tree.start.tsx @@ -0,0 +1,43 @@ +import { PropsWithChildren } from "react"; +import { ArrowDown } from "@/icons"; +import { Badge } from "../badge"; +import { Level } from "../level"; +import { TopicElement } from "./dropdown-tree.types"; +import { cn } from "@/utils/tw-merge"; + +const DROPDOWN_TREE_START_VARIANT = { + default: "rustlanges-dropdown-tree-start--default", + extended: "rustlanges-dropdown-tree-start--extended", +}; + +type StartProps = { + variant: keyof typeof DROPDOWN_TREE_START_VARIANT; +} & PropsWithChildren & + TopicElement; + +export const DropdownTreeStart = ({ + level, + variant, + title, + state, + children, +}: StartProps) => { + return ( +
+ + + + + {title} + + + +
{children}
+
+ ); +}; diff --git a/js/react/lib/components/dropdown-tree/dropdown-tree.subject.tsx b/js/react/lib/components/dropdown-tree/dropdown-tree.subject.tsx new file mode 100644 index 0000000..5a13aea --- /dev/null +++ b/js/react/lib/components/dropdown-tree/dropdown-tree.subject.tsx @@ -0,0 +1,33 @@ +import { InputHTMLAttributes, PropsWithChildren } from "react"; +import { ArrowDown } from "@/icons"; +import { Badge } from "../badge"; +import { Level } from "../level"; +import { TopicElement } from "./dropdown-tree.types"; +import { Radio } from "../radio"; + +type SubjectProps = PropsWithChildren & + TopicElement & + InputHTMLAttributes; +export const DropdownTreeSubject = ({ + level, + title, + state, + children, + ...rest +}: SubjectProps) => { + return ( +
+ + + + + {title} + + + + + +
{children}
+
+ ); +}; diff --git a/js/react/lib/components/dropdown-tree/dropdown-tree.subtopic.tsx b/js/react/lib/components/dropdown-tree/dropdown-tree.subtopic.tsx new file mode 100644 index 0000000..6adc894 --- /dev/null +++ b/js/react/lib/components/dropdown-tree/dropdown-tree.subtopic.tsx @@ -0,0 +1,22 @@ +import { Badge } from "../badge"; +import { Level } from "../level"; +import { withAs } from "@/utils/hoc"; +import { TopicElement } from "./dropdown-tree.types"; + +export const DropdownTreeSubTopic = withAs( + (Component, { level, state, title, ...rest }: TopicElement) => { + return ( + + + + {title} + + + + ); + } +); diff --git a/js/react/lib/components/dropdown-tree/dropdown-tree.topic.tsx b/js/react/lib/components/dropdown-tree/dropdown-tree.topic.tsx new file mode 100644 index 0000000..93872f8 --- /dev/null +++ b/js/react/lib/components/dropdown-tree/dropdown-tree.topic.tsx @@ -0,0 +1,28 @@ +import { PropsWithChildren } from "react"; +import { ArrowDown } from "@/icons"; +import { Badge } from "../badge"; +import { Level } from "../level"; +import { TopicElement } from "./dropdown-tree.types"; + +type DropdownTreeTopicProps = PropsWithChildren & TopicElement; + +export const DropdownTreeTopic = ({ + level, + title, + state, + children, +}: DropdownTreeTopicProps) => { + return ( +
+ + + + {title} + + + + +
{children}
+
+ ); +}; diff --git a/js/react/lib/components/topic/topic.types.ts b/js/react/lib/components/dropdown-tree/dropdown-tree.types.ts similarity index 100% rename from js/react/lib/components/topic/topic.types.ts rename to js/react/lib/components/dropdown-tree/dropdown-tree.types.ts diff --git a/js/react/lib/components/dropdown-tree/index.ts b/js/react/lib/components/dropdown-tree/index.ts new file mode 100644 index 0000000..6f71608 --- /dev/null +++ b/js/react/lib/components/dropdown-tree/index.ts @@ -0,0 +1,13 @@ +import { DropdownTreeStart } from "./dropdown-tree.start"; +import { DropdownTreeSubject } from "./dropdown-tree.subject"; +import { DropdownTreeTopic } from "./dropdown-tree.topic"; +import { DropdownTreeSubTopic } from "./dropdown-tree.subtopic"; +import { DropdownTreeEnd } from "./dropdown-tree.end"; + +export const DropdownTree = { + Start: DropdownTreeStart, + Subject: DropdownTreeSubject, + Topic: DropdownTreeTopic, + SubTopic: DropdownTreeSubTopic, + End: DropdownTreeEnd, +}; diff --git a/js/react/lib/components/topic/index.ts b/js/react/lib/components/topic/index.ts deleted file mode 100644 index cf409b7..0000000 --- a/js/react/lib/components/topic/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./topic.component"; -export * from "./subtopic.component"; -export * from "./topic.types"; diff --git a/js/react/lib/components/topic/subtopic.component.tsx b/js/react/lib/components/topic/subtopic.component.tsx deleted file mode 100644 index 428ee79..0000000 --- a/js/react/lib/components/topic/subtopic.component.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Badge } from "../badge"; -import { Level } from "../level"; -import { TopicElement } from "./topic.types"; -import { withAs } from "@/utils/hoc"; - -export const SubTopic = withAs( - (Component, { level, state, title, ...rest }: TopicElement) => { - return ( - - - {title} - - - ); - } -); diff --git a/js/react/lib/components/topic/topic.component.tsx b/js/react/lib/components/topic/topic.component.tsx deleted file mode 100644 index 573cb4e..0000000 --- a/js/react/lib/components/topic/topic.component.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PropsWithChildren } from "react"; -import { ArrowDown } from "@/icons"; -import { Badge } from "../badge"; -import { Level } from "../level"; -import { TopicElement } from "./topic.types"; - -type TopicProps = PropsWithChildren & TopicElement; -export const Topic = ({ level, title, state, children }: TopicProps) => { - return ( -
- - - {title} - - - - {children} -
- ); -}; diff --git a/js/react/lib/index.ts b/js/react/lib/index.ts index 564607d..6f31147 100644 --- a/js/react/lib/index.ts +++ b/js/react/lib/index.ts @@ -11,5 +11,5 @@ export * from "./components/radio"; export * from "./components/badge"; export * from "./components/dropdown"; export * from "./components/calendar"; -export * from "./components/topic"; +export * from "./components/dropdown-tree"; export * from "./icons"; diff --git a/js/react/showcase/App.tsx b/js/react/showcase/App.tsx index e600b74..033ff89 100644 --- a/js/react/showcase/App.tsx +++ b/js/react/showcase/App.tsx @@ -14,11 +14,10 @@ import { DropdownState, Calendar, CalendarRangeDate, - Topic, - SubTopic, + DropdownTree, } from "@rustlanges/react"; import { ShowComponent } from "./ShowComponent"; -import { useState } from "react"; +import { Fragment, useState } from "react"; const collaborator = { avatarUrl: @@ -26,6 +25,105 @@ const collaborator = { nickname: "Colaborador", }; +const tree = { + title: "Introducción a Rust", + state: "completed" as const, + level: "n1" as const, + subjects: [ + { + title: "Aprende lo básico", + state: "completed" as const, + level: "n1" as const, + topics: [ + { + title: "Sintaxis básica", + state: "completed" as const, + level: "n1" as const, + subtopics: [ + { + title: "Variables y declaraciones", + state: "completed" as const, + level: "n1" as const, + }, + { + title: "Constantes y variables estáticas", + state: "completed" as const, + level: "n1" as const, + }, + { + title: "Shadowing", + state: "completed" as const, + level: "n1" as const, + }, + { + title: "Control de flujo", + state: "completed" as const, + level: "n1" as const, + }, + ], + }, + { + title: "Ownership y Borrowing", + state: "completed" as const, + level: "n1" as const, + }, + { + title: "Tipos de datos primitivos", + state: "completed" as const, + level: "n1" as const, + }, + { + title: "Tipos de datos complejos", + state: "completed" as const, + level: "n1" as const, + }, + ], + }, + { + title: "Manejo de errores", + state: "completed" as const, + level: "n2" as const, + topics: [], + }, + { + title: "Cargo", + state: "completed" as const, + level: "n1" as const, + topics: [], + }, + { + title: "Traits", + state: "completed" as const, + level: "n1" as const, + topics: [], + }, + { + title: "Punteros inteligentes", + state: "completed" as const, + level: "n2" as const, + topics: [], + }, + { + title: "Concurrencia y Paralelismo", + state: "completed" as const, + level: "n2" as const, + topics: [], + }, + { + title: "Interoperabilidad", + state: "completed" as const, + level: "op" as const, + topics: [], + }, + { + title: "Ecosistemas y librerías", + state: "completed" as const, + level: "op" as const, + topics: [], + }, + ], +}; + export function App() { const [single, setSingle] = useState(new Date()); const [multiple, setMultiple] = useState | null>(null); @@ -281,40 +379,107 @@ export function App() { - - + +
+
+ + {tree.subjects.map(subject => ( + + {subject.topics.map(topic => ( + + {topic.subtopics?.map(subtopic => ( + + ))} + + ))} + + ))} + + + Conoce todos nuestros proyectos Open Source en + los que puedes contribuir y potenciar tu aprendizaje 🚀 + +
+ +
+ + {tree.subjects.map(subject => ( + + {subject.topics.map(topic => ( + + {topic.subtopics?.map(subtopic => ( + + ))} + + ))} + + ))} + + + {tree.subjects.map(subject => ( + + {subject.topics.map(topic => ( + + {topic.subtopics?.map(subtopic => ( + + ))} + + ))} + + ))} + + + Conoce todos nuestros proyectos Open Source en + los que puedes contribuir y potenciar tu aprendizaje 🚀 + +
+
+
); } diff --git a/js/react/showcase/styles.css b/js/react/showcase/styles.css index f9224c1..6842d94 100644 --- a/js/react/showcase/styles.css +++ b/js/react/showcase/styles.css @@ -4,7 +4,3 @@ html { background: var(--color-gray-300); } - -@utility shadow-brutal { - box-shadow: 2px 2px 0px #000; -} diff --git a/styles/components.css b/styles/components.css index fb78fe5..8f27202 100644 --- a/styles/components.css +++ b/styles/components.css @@ -9,4 +9,8 @@ @import "./components/radio.css"; @import "./components/tag.css"; @import "./components/text.css"; -@import "./components/topic.css"; +@import "./components/dropdown-tree-start.css"; +@import "./components/dropdown-tree-subject.css"; +@import "./components/dropdown-tree-topic.css"; +@import "./components/dropdown-tree-subtopic.css"; +@import "./components/dropdown-tree-end.css"; diff --git a/styles/components/button.css b/styles/components/button.css index fc11c39..7b45200 100644 --- a/styles/components/button.css +++ b/styles/components/button.css @@ -49,7 +49,7 @@ } .rustlanges-button--icon { - @apply p-0! size-10 aspect-square rounded-full border; + @apply p-0! aspect-square size-10 rounded-full border; @apply bg-light border-black text-black; @variant hover { diff --git a/styles/components/dropdown-tree-end.css b/styles/components/dropdown-tree-end.css new file mode 100644 index 0000000..3459504 --- /dev/null +++ b/styles/components/dropdown-tree-end.css @@ -0,0 +1,11 @@ +@layer component { + .dropdown-tree-end { + @apply shadow-brutal grid h-fit w-full rounded-xl border border-black px-4 py-2.5 text-left; + @apply bg-secondary-50 text-neutral-950; + @apply dark:bg-secondary-950 dark:text-neutral-50; + + & > span:first-child { + @apply flex items-center justify-between; + } + } +} diff --git a/styles/components/dropdown-tree-start.css b/styles/components/dropdown-tree-start.css new file mode 100644 index 0000000..0b0550e --- /dev/null +++ b/styles/components/dropdown-tree-start.css @@ -0,0 +1,44 @@ +@layer component { + .rustlanges-dropdown-tree-start { + @apply box-border h-fit w-full; + + & > summary { + @apply grid w-full grid-cols-[auto_1fr_auto] items-center gap-2 px-4 py-2.5 marker:hidden; + @apply [&>*:first-child]:col-span-3; + + & > svg { + @apply duration-400 size-6 text-neutral-950 transition; + @apply dark:text-neutral-50; + } + } + + &[open] > summary > svg { + @apply -rotate-180; + } + } + + .rustlanges-dropdown-tree-start__title { + @apply text-neutral-950 dark:text-neutral-50; + } + + .rustlanges-dropdown-tree-start--extended { + @apply shadow-brutal rounded-xl border border-black; + @apply dark:bg-secondary-950 bg-secondary-50; + } + + .rustlanges-dropdown-tree-start--default { + & > summary { + @apply shadow-brutal mb-4 gap-4 rounded-2xl border border-black; + @apply dark:bg-secondary-950 bg-secondary-50; + } + + & > div { + @apply relative grid gap-4; + + &::before { + content: ""; + @apply absolute left-[9px] h-full w-0.5 bg-black; + } + } + } +} diff --git a/styles/components/dropdown-tree-subject.css b/styles/components/dropdown-tree-subject.css new file mode 100644 index 0000000..55c5a07 --- /dev/null +++ b/styles/components/dropdown-tree-subject.css @@ -0,0 +1,32 @@ +@layer component { + .rustlanges-dropdown-tree-subject { + @apply shadow-brutal relative ml-6 flex items-center rounded-xl border border-black; + @apply bg-light; + @apply dark:bg-dark; + + & > summary { + @apply relative grid w-full grid-cols-[auto_1fr_auto] items-center gap-2 px-4 py-2.5 marker:hidden; + @apply [&>*:first-child]:col-span-3; + + & > input[type="radio"] { + @apply absolute -left-6 z-10; + } + + & > svg { + @apply duration-400 size-6 text-neutral-950 transition; + @apply dark:text-neutral-50; + } + } + + &[open] > summary > svg { + @apply -rotate-180; + } + } + .rustlanges-dropdown-tree-subject__title { + @apply text-neutral-950 dark:text-neutral-50; + } + + .rustlanges-dropdown-tree-subject--extended { + @apply dark:bg-secondary-950 bg-secondary-50; + } +} diff --git a/styles/components/dropdown-tree-subtopic.css b/styles/components/dropdown-tree-subtopic.css new file mode 100644 index 0000000..21cdd1c --- /dev/null +++ b/styles/components/dropdown-tree-subtopic.css @@ -0,0 +1,16 @@ +@layer components { + .rustlanges-dropdown-tree-subtopic { + @apply grid w-full grid-cols-[auto_1fr_auto] items-center gap-2 border-b px-6 py-2.5; + @apply border-b-gray dark:border-b-neutral-600; + + &:last-child { + @apply border-b-transparent; + } + } + + .rustlanges-dropdown-tree-subtopic__title { + @apply text-neutral-950; + @apply desktop:text-sm text-xs; + @apply dark:text-neutral-50; + } +} diff --git a/styles/components/dropdown-tree-topic.css b/styles/components/dropdown-tree-topic.css new file mode 100644 index 0000000..f372f0f --- /dev/null +++ b/styles/components/dropdown-tree-topic.css @@ -0,0 +1,26 @@ +@layer component { + .rustlanges-dropdown-tree-topic { + @apply w-full; + + &:last-child > summary { + @apply border-b-transparent; + } + + & > summary { + @apply grid w-full grid-cols-[auto_1fr_auto_auto] items-center gap-2 border-y border-black px-3 py-4 marker:hidden; + + & > svg { + @apply duration-400 size-6 text-neutral-950 transition; + @apply dark:text-neutral-50; + } + } + + &[open] > summary > svg { + @apply -rotate-180; + } + } + + .rustlanges-dropdown-tree-topic__title { + @apply text-neutral-950 dark:text-neutral-50; + } +} diff --git a/styles/components/topic.css b/styles/components/topic.css deleted file mode 100644 index daaee30..0000000 --- a/styles/components/topic.css +++ /dev/null @@ -1,35 +0,0 @@ -@layer component { - .rustlanges-topic { - @apply w-full; - } - .rustlanges-topic[open] svg { - @apply -rotate-180; - } - - .rustlanges-topic__summary::-webkit-details-marker { - display: none; - } - - .rustlanges-topic__summary { - @apply grid w-full grid-cols-[auto_1fr_auto_auto] items-center gap-2 border-y border-black px-3 py-4; - - & > svg { - @apply duration-400 size-6 transition; - } - } - - .rustlanges-topic__title { - @apply text-neutral-950 dark:text-neutral-50; - } - - .rustlanges-subtopic { - @apply grid w-full grid-cols-[auto_1fr_auto] items-center gap-2 border-b px-6 py-2.5; - @apply border-b-gray dark:border-b-neutral-600; - } - - .rustlanges-subtopic__title { - @apply text-neutral-950; - @apply desktop:text-sm text-xs; - @apply dark:text-neutral-50; - } -} diff --git a/styles/utilities/shadow.css b/styles/utilities/shadow.css index ef35839..5c91ae0 100644 --- a/styles/utilities/shadow.css +++ b/styles/utilities/shadow.css @@ -4,3 +4,7 @@ box-shadow: 2px 2px 0 0 --value(--color- *); } } + +@utility shadow-brutal { + box-shadow: 2px 2px 0 0 #000000; +}