1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 00:21:28 +02:00
Files
developer-roadmap/src/components/RoadmapAIChat/RoadmapTopicList.tsx
Kamran Ahmed 02e7373bcd feat: add floating chat on roadmap pages (#8765)
* Add floating chat

* Refactor roadmap ai chat to hook

* Chat inside floating chat

* Fix bulk update not working

* Add floating chat widget

* Add chat header buttons

* Show a default set of questions

* Populate chat questions at bottom

* Handle chat submission

* Add personalize popup

* Fix body scroll locking issue

* Add scroll to bottom functionality

* Fix focus issue on persona form

* Fix responsiveness of the floating chat

* Final implementation

* Height fixes

* Fix floating ui

* Upgrade flow in floating chat

* Upgrade responsive UI

* Authetnicated checks

* Responsive bottom bar
2025-06-10 19:43:06 +01:00

100 lines
2.7 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree';
import { queryClient } from '../../stores/query-client';
import { Fragment, useMemo } from 'react';
import { ChevronRightIcon } from 'lucide-react';
type TopicListType = {
topicId: string;
};
function parseTopicList(content: string): TopicListType[] {
const items: TopicListType[] = [];
const topicListRegex = /<topic-id>.*?<\/topic-id>/gs;
const topicListItems = content.match(topicListRegex);
if (!topicListItems) {
return items;
}
for (const topicListItem of topicListItems) {
const topicIdRegex = /<topic-id>(.*?)<\/topic-id>/;
const topicId = topicListItem.match(topicIdRegex)?.[1]?.trim();
if (!topicId) {
continue;
}
items.push({
topicId,
});
}
return items;
}
type RoadmapTopicListProps = {
roadmapId: string;
content: string;
onTopicClick?: (topicId: string, topicTitle: string) => void;
};
export function RoadmapTopicList(props: RoadmapTopicListProps) {
const { roadmapId, content, onTopicClick } = props;
const topicListItems = parseTopicList(content);
const { data: roadmapTreeData } = useQuery(
roadmapTreeMappingOptions(roadmapId),
queryClient,
);
const progressItemWithText = useMemo(() => {
return topicListItems.map((item) => {
const roadmapTreeItem = roadmapTreeData?.find(
(mapping) => mapping.nodeId === item.topicId,
);
return {
...item,
text: (roadmapTreeItem?.text || item.topicId)
?.split(' > ')
.slice(1)
.join(' > '),
};
});
}, [topicListItems, roadmapTreeData]);
return (
<div className="relative my-6 flex flex-wrap gap-1 first:mt-0 last:mb-0">
{progressItemWithText.map((item) => {
const labelParts = item.text.split(' > ').slice(-2);
const labelPartCount = labelParts.length;
return (
<button
key={item.topicId}
className="collapse-if-empty flex items-center gap-1 rounded-lg border border-gray-200 bg-white p-1 px-2 text-left text-sm hover:bg-gray-50"
onClick={() => {
onTopicClick?.(item.topicId, item.text);
}}
>
{labelParts.map((part, index) => {
return (
<Fragment key={index}>
<span>{part}</span>
{index < labelPartCount - 1 && (
<ChevronRightIcon
className="size-3 text-gray-400"
strokeWidth={2.5}
/>
)}
</Fragment>
);
})}
</button>
);
})}
</div>
);
}