diff --git a/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx
new file mode 100644
index 000000000..9aca8b006
--- /dev/null
+++ b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx
@@ -0,0 +1,54 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2021 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+
+import { renderWithTheme } from '@looker/components-test-utils'
+import { screen, waitFor } from '@testing-library/react'
+import React from 'react'
+import userEvent from '@testing-library/user-event'
+import { CopyLinkButton } from '../CopyLinkButton'
+
+describe('CopyLinkButton', () => {
+ test('it renders', () => {
+ renderWithTheme()
+ expect(screen.getByText('Copy link to this page view')).toBeInTheDocument()
+ })
+ const mockClipboardCopy = jest
+ .fn()
+ .mockImplementation(() => Promise.resolve())
+ Object.assign(navigator, {
+ clipboard: {
+ writeText: mockClipboardCopy,
+ },
+ })
+ test('it copies to clipboard', async () => {
+ jest.spyOn(navigator.clipboard, 'writeText')
+ renderWithTheme()
+ await waitFor(() => {
+ userEvent.click(screen.getByRole('button'))
+ expect(mockClipboardCopy).toHaveBeenCalledWith(location.href)
+ })
+ })
+})
diff --git a/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx
new file mode 100644
index 000000000..29ed56c18
--- /dev/null
+++ b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx
@@ -0,0 +1,68 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2021 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+import React, { useState } from 'react'
+import { IconButton } from '@looker/components'
+import { Link } from '@styled-icons/material-outlined/Link'
+import styled from 'styled-components'
+
+interface DocumentInterfaceProps {
+ top: string
+ right: string
+ visible: boolean
+}
+export const CopyLinkButton = ({
+ top,
+ right,
+ visible,
+}: DocumentInterfaceProps) => {
+ const [title, CopyLinkTooltip] = useState('Copy link to this page view')
+ return (
+
+ {
+ CopyLinkTooltip('Copied to clipboard')
+ await navigator.clipboard.writeText(location.href)
+ }}
+ onMouseEnter={() => CopyLinkTooltip('Copy link to this section')}
+ size="small"
+ icon={}
+ label={title}
+ tooltipPlacement="bottom"
+ />
+
+ )
+}
+
+const CopyLink = styled('span')<{
+ top: string
+ right: string
+ visible: boolean
+}>`
+ position: absolute;
+ top: ${({ top }) => top};
+ right: ${({ right }) => right};
+ display: ${({ visible }) => (visible ? 'block' : 'none')};
+`
diff --git a/packages/api-explorer/src/components/CopyLinkButton/index.ts b/packages/api-explorer/src/components/CopyLinkButton/index.ts
new file mode 100644
index 000000000..98548aea9
--- /dev/null
+++ b/packages/api-explorer/src/components/CopyLinkButton/index.ts
@@ -0,0 +1,26 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2021 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+export { CopyLinkButton } from './CopyLinkButton'
diff --git a/packages/api-explorer/src/components/CopyLinkWrapper/CopyLinkWrapper.tsx b/packages/api-explorer/src/components/CopyLinkWrapper/CopyLinkWrapper.tsx
new file mode 100644
index 000000000..0311b6e3c
--- /dev/null
+++ b/packages/api-explorer/src/components/CopyLinkWrapper/CopyLinkWrapper.tsx
@@ -0,0 +1,76 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2021 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+import React, { ReactNode, ReactNodeArray, useState, useCallback } from 'react'
+import { IconButton, Space } from '@looker/components'
+import { Link } from '@styled-icons/material-outlined/Link'
+
+interface CopyLinkWrapperProps {
+ visible?: boolean
+ children: ReactNode | ReactNodeArray
+}
+
+const COPY_TO_CLIPBOARD = 'Copied to clipboard'
+const COPY_TO_SECTION = 'Copy link to this section'
+
+export const CopyLinkWrapper = ({
+ visible = true,
+ children,
+}: CopyLinkWrapperProps) => {
+ const [showCopyLinkButton, setShowCopyLinkButton] = useState(false)
+ const [tooltipContent, setTooltipContent] = useState(COPY_TO_SECTION)
+
+ const onCopyLink = useCallback(async () => {
+ setTooltipContent(COPY_TO_CLIPBOARD)
+ // TODO - this wont work in the extension - there are other mechanisms
+ await navigator.clipboard.writeText(location.href)
+ }, [setTooltipContent])
+
+ const onCopyButtonMouseLeave = useCallback(async () => {
+ setTooltipContent(COPY_TO_SECTION)
+ }, [setTooltipContent])
+
+ return (
+ <>
+ setShowCopyLinkButton(true)}
+ onMouseLeave={() => setShowCopyLinkButton(false)}
+ width="100%"
+ >
+ {children}
+ {visible && showCopyLinkButton && (
+ }
+ label={tooltipContent}
+ tooltipPlacement="bottom"
+ />
+ )}
+
+ >
+ )
+}
diff --git a/packages/api-explorer/src/components/CopyLinkWrapper/index.ts b/packages/api-explorer/src/components/CopyLinkWrapper/index.ts
new file mode 100644
index 000000000..eda788cf6
--- /dev/null
+++ b/packages/api-explorer/src/components/CopyLinkWrapper/index.ts
@@ -0,0 +1,26 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2021 Looker Data Sciences, Inc.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ */
+export { CopyLinkWrapper } from './CopyLinkWrapper'
diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx
index 17300ab7e..006564f76 100644
--- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx
+++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx
@@ -117,6 +117,18 @@ describe('Search', () => {
})
})
+ test('renders and removes copy link button based on mouse hover', async () => {
+ renderWithRouterAndReduxProvider(, ['/3.1/methods'])
+ expect(screen.getByText('Copy link to this page view')).not.toBeVisible()
+ const searchPattern = 'embedsso'
+ const input = screen.getByLabelText('Search')
+ await userEvent.paste(input, searchPattern)
+ userEvent.hover(input)
+ expect(screen.getByText('Copy link to this page view')).toBeVisible()
+ userEvent.unhover(input)
+ expect(screen.getByText('Copy link to this page view')).not.toBeVisible()
+ })
+
test('sets search default value from store on load', async () => {
const searchPattern = 'embedsso'
const store = createTestStore({
diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx
index 0074e71bb..33a4cd97d 100644
--- a/packages/api-explorer/src/components/SideNav/SideNav.tsx
+++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx
@@ -34,6 +34,7 @@ import {
TabPanels,
useTabs,
InputSearch,
+ Box2,
} from '@looker/components'
import type {
SpecItem,
@@ -51,6 +52,7 @@ import { SideNavMethodTags } from './SideNavMethodTags'
import { SideNavTypeTags } from './SideNavTypeTags'
import { useDebounce, countMethods, countTypes } from './searchUtils'
import { SearchMessage } from './SearchMessage'
+import { CopyLinkWrapper } from '../CopyLinkWrapper'
interface SideNavState {
tags: TagList
@@ -95,6 +97,7 @@ export const SideNav: FC = ({ headless = false, spec }) => {
const searchCriteria = useSelector(selectSearchCriteria)
const searchPattern = useSelector(selectSearchPattern)
const [pattern, setSearchPattern] = useState(searchPattern)
+ const [showCopyLinkButton, setShowCopyLinkButton] = useState(false)
const debouncedPattern = useDebounce(pattern, 250)
const [sideNavState, setSideNavState] = useState(() => ({
tags: spec?.api?.tags || {},
@@ -107,6 +110,7 @@ export const SideNav: FC = ({ headless = false, spec }) => {
const handleInputChange = (value: string) => {
setSearchPattern(value)
+ setShowCopyLinkButton(!!value)
}
useEffect(() => {
@@ -160,18 +164,25 @@ export const SideNav: FC = ({ headless = false, spec }) => {
return (