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

Fork #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/src/components/ElementsAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ElementsAPI: React.FC = () => {

return (
<Box flex={1} overflowY={layout !== 'stacked' ? 'hidden' : undefined}>
<API apiDescriptionUrl={specUrlWithProxy} router="hash" layout={layout} />
<API apiDescriptionUrl={specUrlWithProxy} router="hash" layout={layout} useOperationPathInList={true} />
</Box>
);
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@
"elements": "yarn workspace @stoplight/elements",
"elements-core": "yarn workspace @stoplight/elements-core",
"elements-dev-portal": "yarn workspace @stoplight/elements-dev-portal",
"build": "yarn workspace @stoplight/elements-core build && yarn workspace @stoplight/elements build && yarn workspace @stoplight/elements-dev-portal build",
"build": "yarn workspace @fifteen/stoplight-elements-core build && yarn workspace @fifteen/stoplight-elements build && yarn workspace @stoplight/elements-dev-portal build",
"build.docs": "yarn workspace @stoplight/elements build.docs",
"postbuild": "yarn workspace @stoplight/elements test.packaging && yarn workspace @stoplight/elements-core test.packaging && yarn workspace @stoplight/elements-dev-portal test.packaging",
"postbuild": "yarn workspace @fifteen/stoplight-elements test.packaging && yarn workspace @fifteen/stoplight-elements-core test.packaging && yarn workspace @stoplight/elements-dev-portal test.packaging",
"lint": "eslint '{packages,examples}/*/src/**/*.{ts,tsx}'",
"version": "lerna version --no-push",
"release": "lerna publish from-package --yes --registry https://registry.npmjs.org --loglevel silly",
Expand Down Expand Up @@ -125,5 +125,5 @@
"@commitlint/config-conventional"
]
},
"dependencies": {}
"packageManager": "[email protected]"
}
4 changes: 2 additions & 2 deletions packages/elements-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stoplight/elements-core",
"version": "8.4.6",
"name": "@fifteen/stoplight-elements-core",
"version": "8.4.6-fifteen3",
"sideEffects": [
"web-components.min.js",
"src/web-components/**",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Flex, Heading } from '@stoplight/mosaic';
import { Box, Flex, Heading, Input } from '@stoplight/mosaic';
import * as React from 'react';
import { Link, useLocation } from 'react-router-dom';

Expand Down Expand Up @@ -115,6 +115,8 @@ export const Sidebar = ({
onTocClick?(): void;
isInResponsiveMode: boolean;
}) => {
const [filter, setFilter] = React.useState('');

return (
<>
<Flex ml={4} mb={5} alignItems="center">
Expand All @@ -125,13 +127,29 @@ export const Sidebar = ({
)}
<Heading size={4}>{name}</Heading>
</Flex>
<Flex ml={4} mb={5} alignItems="center">
<Input
appearance="default"
placeholder="Filter routes"
onMouseDown={e => e.currentTarget.focus()}
onKeyDown={e => e.key === 'Enter' && e.currentTarget.blur()}
flex={1}
bg="canvas-100"
rounded
value={filter}
onChange={e => {
setFilter(e.currentTarget.value);
}}
/>
</Flex>
<Flex flexGrow flexShrink overflowY="auto" direction="col">
<TableOfContents
tree={tree}
activeId={pathname}
Link={Link}
onLinkClick={onTocClick}
isInResponsiveMode={isInResponsiveMode}
filter={filter}
/>
</Flex>
<PoweredByLink source={name} pathname={pathname} packageType="elements" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const TableOfContents = React.memo<TableOfContentsProps>(
maxDepthOpenByDefault,
externalScrollbar = false,
isInResponsiveMode = false,
filter = '',
onLinkClick,
}) => {
const container = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -95,6 +96,7 @@ export const TableOfContents = React.memo<TableOfContentsProps>(
onLinkClick={onLinkClick}
isInResponsiveMode={isInResponsiveMode}
makeSlugAbsoluteRoute={makeSlugAbsoluteRoute}
filter={filter}
/>
);
})}
Expand Down Expand Up @@ -135,7 +137,8 @@ const GroupItem = React.memo<{
makeSlugAbsoluteRoute?: boolean;
maxDepthOpenByDefault?: number;
onLinkClick?(): void;
}>(({ item, depth, maxDepthOpenByDefault, isInResponsiveMode, makeSlugAbsoluteRoute, onLinkClick }) => {
filter?: string;
}>(({ item, depth, maxDepthOpenByDefault, isInResponsiveMode, makeSlugAbsoluteRoute, onLinkClick, filter }) => {
if (isExternalLink(item)) {
return (
<Box as="a" href={item.url} target="_blank" rel="noopener noreferrer" display="block">
Expand All @@ -156,9 +159,15 @@ const GroupItem = React.memo<{
onLinkClick={onLinkClick}
isInResponsiveMode={isInResponsiveMode}
makeSlugAbsoluteRoute={makeSlugAbsoluteRoute}
filter={filter}
/>
);
} else if (isNode(item)) {
} else if (
isNode(item) &&
(item.type !== 'http_operation' ||
(filter ?? '') === '' ||
item.title.toLowerCase().includes(filter!.toLowerCase()))
) {
return (
<Node
depth={depth}
Expand Down Expand Up @@ -205,104 +214,127 @@ const Group = React.memo<{
isInResponsiveMode?: boolean;
makeSlugAbsoluteRoute?: boolean;
onLinkClick?(): void;
}>(({ depth, item, maxDepthOpenByDefault, isInResponsiveMode, makeSlugAbsoluteRoute, onLinkClick = () => {} }) => {
const activeId = React.useContext(ActiveIdContext);
const [isOpen, setIsOpen] = React.useState(() => isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault));
const hasActive = !!activeId && hasActiveItem(item.items, activeId);

// If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode)
React.useEffect(() => {
const openByDefault = isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault);
if (isOpen !== openByDefault) {
setIsOpen(openByDefault);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [depth, maxDepthOpenByDefault]);
filter?: string;
}>(
({
depth,
item,
maxDepthOpenByDefault,
isInResponsiveMode,
makeSlugAbsoluteRoute,
onLinkClick = () => {},
filter,
}) => {
const activeId = React.useContext(ActiveIdContext);
const [isOpen, setIsOpen] = React.useState(() =>
isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault),
);
const hasActive = !!activeId && hasActiveItem(item.items, activeId);

// Expand group when it has the active item
React.useEffect(() => {
if (hasActive) {
setIsOpen(true);
}
}, [hasActive]);
// If maxDepthOpenByDefault changes, we want to update all the isOpen states (used in live preview mode)
React.useEffect(() => {
const openByDefault = isGroupOpenByDefault(depth, item, activeId, maxDepthOpenByDefault);
if (isOpen !== openByDefault) {
setIsOpen(openByDefault);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [depth, maxDepthOpenByDefault]);

const handleClick = (e: React.MouseEvent, forceOpen?: boolean) => {
setIsOpen(forceOpen ? true : !isOpen);
};
// Expand group when it has the active item
React.useEffect(() => {
if (hasActive) {
setIsOpen(true);
}
}, [hasActive]);

const meta = (
<Flex alignItems="center">
{isNodeGroup(item) && item.version && <Version value={item.version} />}
<Box
as={Icon}
icon={['fas', isOpen ? 'chevron-down' : 'chevron-right']}
color="muted"
fixedWidth
onClick={(e: React.MouseEvent) => {
// Don't propagate event when clicking icon
e.stopPropagation();
e.preventDefault();
handleClick(e);
}}
/>
</Flex>
);
const handleClick = (e: React.MouseEvent, forceOpen?: boolean) => {
setIsOpen(forceOpen ? true : !isOpen);
};

// Show the Group as active when group has active item and is closed
const showAsActive = hasActive && !isOpen;
let elem;
if (isNodeGroup(item)) {
elem = (
<Node
depth={depth}
item={item}
meta={meta}
showAsActive={showAsActive}
onClick={handleClick}
onLinkClick={onLinkClick}
isInResponsiveMode={isInResponsiveMode}
makeSlugAbsoluteRoute={makeSlugAbsoluteRoute}
/>
const meta = (
<Flex alignItems="center">
{isNodeGroup(item) && item.version && <Version value={item.version} />}
<Box
as={Icon}
icon={['fas', isOpen ? 'chevron-down' : 'chevron-right']}
color="muted"
fixedWidth
onClick={(e: React.MouseEvent) => {
// Don't propagate event when clicking icon
e.stopPropagation();
e.preventDefault();
handleClick(e);
}}
/>
</Flex>
);
} else {
elem = (
<Item
isInResponsiveMode={isInResponsiveMode}
title={item.title}
meta={meta}
onClick={handleClick}
depth={depth}
isActive={showAsActive}
icon={
item.itemsType &&
NODE_GROUP_ICON[item.itemsType] && (
<Box as={Icon} color={NODE_GROUP_ICON_COLOR[item.itemsType]} icon={NODE_GROUP_ICON[item.itemsType]} />
)
}
/>

// Show the Group as active when group has active item and is closed
const showAsActive = hasActive && !isOpen;
let elem;
if (isNodeGroup(item)) {
elem = (
<Node
depth={depth}
item={item}
meta={meta}
showAsActive={showAsActive}
onClick={handleClick}
onLinkClick={onLinkClick}
isInResponsiveMode={isInResponsiveMode}
makeSlugAbsoluteRoute={makeSlugAbsoluteRoute}
/>
);
} else {
elem = (
<Item
isInResponsiveMode={isInResponsiveMode}
title={item.title}
meta={meta}
onClick={handleClick}
depth={depth}
isActive={showAsActive}
icon={
item.itemsType &&
NODE_GROUP_ICON[item.itemsType] && (
<Box as={Icon} color={NODE_GROUP_ICON_COLOR[item.itemsType]} icon={NODE_GROUP_ICON[item.itemsType]} />
)
}
/>
);
}

let filteredItems = item.items.filter(
e =>
!isNode(e) ||
e.type !== 'http_operation' ||
(filter ?? '') === '' ||
e.title.toLowerCase().includes(filter!.toLowerCase()),
);
}

return (
<>
{elem}
if (filteredItems.length === 0) {
return null;
}

{isOpen &&
item.items.map((groupItem, key) => {
return (
return (
<>
{elem}
{isOpen &&
filteredItems.map((groupItem, key) => (
<GroupItem
key={key}
item={groupItem}
depth={depth + 1}
onLinkClick={onLinkClick}
isInResponsiveMode={isInResponsiveMode}
makeSlugAbsoluteRoute={makeSlugAbsoluteRoute}
filter={filter}
/>
);
})}
</>
);
});
))}
</>
);
},
);
Group.displayName = 'Group';

const Item = React.memo<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type TableOfContentsProps = {
maxDepthOpenByDefault?: number;
externalScrollbar?: boolean;
isInResponsiveMode?: boolean;
filter?: string;
onLinkClick?(): void;
};

Expand Down
7 changes: 6 additions & 1 deletion packages/elements-core/src/components/TryIt/build-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ export const getQueryParams = ({
throw Error();
}
} catch (e) {
throw new Error(`Cannot use param value "${value}". JSON array expected.`);
if (/^[\w\.-]+(,[\w\.-]+)*,?$/.test(value)) {
if (value.endsWith(',')) nested = value.slice(0, -1).split(',');
else nested = value.split(',');
} else {
throw new Error(`Cannot use param value "${value}". JSON array expected.`);
}
}

if (explode) {
Expand Down
4 changes: 2 additions & 2 deletions packages/elements/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stoplight/elements",
"version": "8.4.6",
"name": "@fifteen/stoplight-elements",
"version": "8.4.6-fifteen3",
"description": "UI components for composing beautiful developer documentation.",
"keywords": [],
"sideEffects": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type SidebarLayoutProps = {
hideSamples?: boolean;
hideSchemas?: boolean;
hideInternal?: boolean;
useOperationPathInList?: boolean;
hideExport?: boolean;
hideServerInfo?: boolean;
hideSecurityInfo?: boolean;
Expand All @@ -42,14 +43,15 @@ export const APIWithResponsiveSidebarLayout: React.FC<SidebarLayoutProps> = ({
hideExport,
hideServerInfo,
hideSecurityInfo,
useOperationPathInList,
exportProps,
tryItCredentialsPolicy,
tryItCorsProxy,
renderExtensionAddon,
}) => {
const container = React.useRef<HTMLDivElement>(null);
const tree = React.useMemo(
() => computeAPITree(serviceNode, { hideSchemas, hideInternal }),
() => computeAPITree(serviceNode, { hideSchemas, hideInternal, useOperationPathInList }),
[serviceNode, hideSchemas, hideInternal],
);
const location = useLocation();
Expand Down
Loading