Skip to content

Commit

Permalink
feature(webui): display both imports and importedBy module inform…
Browse files Browse the repository at this point in the history
…ation (#45)

* refactor(webui): rework the module imported by component to reuse for imports

* refactor(webui): integrate empty state in module reference list

* feature(webui): add both `imports` and `importedBy` module information
  • Loading branch information
byCedric committed Apr 30, 2024
1 parent 02c242b commit 653983d
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 14 deletions.
Binary file modified webui/bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions webui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@expo/styleguide": "^8.2.2",
"@expo/styleguide-native": "^7.0.1",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-select": "^2.0.0",
Expand All @@ -41,6 +42,7 @@
"react-native-svg": "15.2.0-rc.0",
"react-native-web": "~0.19.6",
"shiki": "^1.2.4",
"tailwind-gradient-mask-image": "^1.2.0",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
9 changes: 2 additions & 7 deletions webui/src/app/(atlas)/[bundle]/modules/[path].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useLocalSearchParams } from 'expo-router';
import { BreadcrumbLinks } from '~/components/BreadcrumbLinks';
import { BundleSelectForm } from '~/components/BundleSelectForm';
import { ModuleCode } from '~/components/ModuleCode';
import { ModuleImportedBy } from '~/components/ModuleImportedBy';
import { ModuleReference } from '~/components/ModuleReference';
import { PropertySummary } from '~/components/PropertySummary';
import { DataErrorState, NoDataState } from '~/components/StateInfo';
import { BundleDeltaToast, useBundle } from '~/providers/bundle';
Expand Down Expand Up @@ -47,12 +47,7 @@ export default function ModulePage() {
<NoDataState title="Module not found." />
) : (
<div className="mx-6 mb-4">
{!!module.data.importedBy?.length && (
<div className="mb-2 my-6">
<h3 className="font-semibold mx-2">Imported by</h3>
<ModuleImportedBy bundle={bundle} module={module.data} />
</div>
)}
<ModuleReference className="mb-2 my-6" bundle={bundle} module={module.data} />
<div className="mx-2 my-8">
<h3 className="font-semibold my-2">Module content</h3>
<ModuleCode module={module.data} />
Expand Down
113 changes: 113 additions & 0 deletions webui/src/components/ModuleReference.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { cva, cx } from 'class-variance-authority';
// @ts-expect-error
import ChevronDownIcon from 'lucide-react/dist/esm/icons/chevron-down';
import { type ComponentProps, useState, useRef, useLayoutEffect } from 'react';

import { ModuleReferenceList } from './ModuleReferenceList';

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/ui/Collapsible';
import type { AtlasModule, PartialAtlasBundle } from '~core/data/types';

type ModuleReferenceProps = {
bundle: PartialAtlasBundle;
module: AtlasModule;
className?: string;
};

export function ModuleReference(props: ModuleReferenceProps) {
return (
<div className={cx('lg:grid lg:grid-cols-2', props.className)}>
<div className="mb-6 lg:mb-0">
<ModuleReferenceCollapsible
title="Modules imported"
emptyDescription="This module does not import other modules."
bundle={props.bundle}
moduleRefs={props.module.imports}
/>
</div>
<div className="mt-6 lg:mt-0">
<ModuleReferenceCollapsible
title="Imported by modules"
emptyDescription="This module is not imported by other modules."
bundle={props.bundle}
moduleRefs={props.module.importedBy}
/>
</div>
</div>
);
}

type ModuleReferenceCollapsibleProps = ComponentProps<typeof ModuleReferenceList> & {
title: string;
emptyDescription: string;
};

const collapsibleContentVariants = cva('', {
variants: {
variants: {
overflow: 'overflow-hidden gradient-mask-b-50',
},
state: {
open: '',
closed: 'max-h-28',
},
},
});

function ModuleReferenceCollapsible(props: ModuleReferenceCollapsibleProps) {
const contentRef = useRef<HTMLDivElement>(null);
const [isOverflowing, setOverflowing] = useState(false);
const [isOpen, setOpen] = useState(!props.moduleRefs.length);

useLayoutEffect(() => {
/** Last known overflowing height, used to detect when collapsible is open */
let knownOverflowHeight: number | null = null;

const $ref = contentRef.current;
const detectOverflow = () => {
if (!$ref) return;

// Detect overflow only when collapsible is closed
if ($ref.dataset.state === 'closed') {
knownOverflowHeight = $ref.offsetHeight;
setOverflowing($ref.offsetHeight < $ref.scrollHeight);
} else if (knownOverflowHeight !== null) {
setOverflowing(knownOverflowHeight < $ref.scrollHeight);
}
};

detectOverflow();
window.addEventListener('resize', detectOverflow);
return () => window.removeEventListener('resize', detectOverflow);
}, [contentRef.current]);

return (
<Collapsible open={isOpen} disabled={!isOverflowing} onOpenChange={setOpen}>
<CollapsibleTrigger>
<div className="flex flex-row items-center">
<h3 className="font-semibold mx-2">{props.title}</h3>
<ChevronDownIcon
size={18}
className={cx(
'text-icon-secondary transition-transform invisible',
isOverflowing && '!visible',
isOpen && 'rotate-180'
)}
/>
</div>
</CollapsibleTrigger>
<CollapsibleContent
forceMount
ref={contentRef}
className={collapsibleContentVariants({
state: isOpen ? 'open' : 'closed',
variants: !isOpen && isOverflowing ? 'overflow' : undefined,
})}
>
<ModuleReferenceList bundle={props.bundle} moduleRefs={props.moduleRefs}>
<span className="italic text-xs text-secondary">{props.emptyDescription}</span>
</ModuleReferenceList>
</CollapsibleContent>
</Collapsible>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import FileIcon from 'lucide-react/dist/esm/icons/file';
import { PropsWithChildren } from 'react';

import { relativeBundlePath } from '~/utils/bundle';
import type { AtlasModule, AtlasModuleRef, PartialAtlasBundle } from '~core/data/types';
import type { AtlasModuleRef, PartialAtlasBundle } from '~core/data/types';

type ModuleImportedByProps = {
type ModuleReferenceListProps = PropsWithChildren<{
bundle: PartialAtlasBundle;
module: AtlasModule;
};
moduleRefs: AtlasModuleRef[];
}>;

export function ModuleReferenceList(props: ModuleReferenceListProps) {
if (props.moduleRefs.length === 0) {
return <div className="m-2">{props.children}</div>;
}

export function ModuleImportedBy(props: ModuleImportedByProps) {
return (
<>
{props.module.importedBy.map((moduleRef) => (
{props.moduleRefs.map((moduleRef) => (
<div key={moduleRef.path} className="inline-block m-2">
<ModuleImportLink bundle={props.bundle} reference={moduleRef} />
</div>
Expand Down
2 changes: 2 additions & 0 deletions webui/src/ui/Breadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// see: https://ui.shadcn.com/docs/components/breadcrumb

import { Slot } from '@radix-ui/react-slot';
import { cx } from 'class-variance-authority';
// @ts-expect-error
Expand Down
9 changes: 9 additions & 0 deletions webui/src/ui/Collapsible.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// see: https://ui.shadcn.com/docs/components/collapsible

import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';

export const Collapsible = CollapsiblePrimitive.Root;

export const CollapsibleTrigger = CollapsiblePrimitive.Trigger;

export const CollapsibleContent = CollapsiblePrimitive.Content;
2 changes: 1 addition & 1 deletion webui/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ module.exports = {
...expoTheme,
content: ['./src/**/*.{js,jsx,ts,tsx}'],
presets: [require('nativewind/preset')],
plugins: [require('tailwindcss-animate')],
plugins: [require('tailwindcss-animate'), require('tailwind-gradient-mask-image')],
};

0 comments on commit 653983d

Please sign in to comment.