mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 13:11:49 +00:00
sveltekit-prototype: Add separator component and setup storybook (#54674)
This commit adds a simple separator component to be used for resizing panels (e.g. sidebars) and is used on file tree pages. The way the separator works is quite simple atm: It computes its position in relation to the size of the parent element and saves that value in the provided store. The parent component can use this value however it sees fit. The current value is persisted in local storage. I assume that this functionality will be expanded or changed in the future but it's a start. I also setup storybook 7, following the default setup instructions. I left everybody as default except for deleting the example stories it came with. We can always change the structure later. Initially I wasn't able to run `pnpm run storybook`, it was throwing an error about not being able to import `ts-dedent`. Adding it to the root `package.json` file seems to resolve this issue. Repo page: https://github.com/sourcegraph/sourcegraph/assets/179026/fb6ba082-8959-47ed-b2ae-8e1aa351b64c Storybook:  ## Test plan Open repo page and use separator. Run storybooks with `pnpm run storybooks` Run existing storybooks in the workspace roots. It's not impacted by adding a new version.
This commit is contained in:
parent
faabc3a35b
commit
53e7816010
@ -1,13 +1,19 @@
|
||||
const baseConfig = require('../../.eslintrc')
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: '../../.eslintrc.js',
|
||||
extends: ['../../.eslintrc.js', 'plugin:storybook/recommended'],
|
||||
parserOptions: {
|
||||
...baseConfig.parserOptions,
|
||||
project: [__dirname + '/tsconfig.json', __dirname + '/src/**/tsconfig.json'],
|
||||
},
|
||||
plugins: [...baseConfig.plugins, 'svelte3'],
|
||||
overrides: [...baseConfig.overrides, { files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
overrides: [
|
||||
...baseConfig.overrides,
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
processor: 'svelte3/svelte3',
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
...baseConfig.settings,
|
||||
'svelte3/typescript': () => require('typescript'),
|
||||
|
||||
13
client/web-sveltekit/.storybook/main.js
Normal file
13
client/web-sveltekit/.storybook/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
/** @type { import('@storybook/sveltekit').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/sveltekit',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
}
|
||||
export default config
|
||||
14
client/web-sveltekit/.storybook/preview.js
Normal file
14
client/web-sveltekit/.storybook/preview.js
Normal file
@ -0,0 +1,14 @@
|
||||
/** @type { import('@storybook/svelte').Preview } */
|
||||
const preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
@ -1,45 +1,58 @@
|
||||
{
|
||||
"name": "@sourcegraph/web-sveltekit",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:dotcom": "vite dev --mode=dotcom",
|
||||
"dev:oss": "vite dev --mode=oss",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"sync": "svelte-kit sync",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"generate": "pnpm -w generate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.25.0",
|
||||
"@sveltejs/adapter-auto": "^2.1.0",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^4.0.0",
|
||||
"svelte-check": "^3.4.3",
|
||||
"tslib": "2.1.0",
|
||||
"vite": "^4.3.9"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@remix-run/router": "~1.3.2",
|
||||
"@sourcegraph/branded": "workspace:*",
|
||||
"@sourcegraph/common": "workspace:*",
|
||||
"@sourcegraph/http-client": "workspace:*",
|
||||
"@sourcegraph/shared": "workspace:*",
|
||||
"@sourcegraph/web": "workspace:*",
|
||||
"@sourcegraph/wildcard": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"prismjs": "^1.29.0"
|
||||
}
|
||||
"name": "@sourcegraph/web-sveltekit",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:dotcom": "vite dev --mode=dotcom",
|
||||
"dev:oss": "vite dev --mode=oss",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"sync": "svelte-kit sync",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"generate": "pnpm -w generate",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.25.0",
|
||||
"@storybook/addon-essentials": "^7.0.26",
|
||||
"@storybook/addon-interactions": "^7.0.26",
|
||||
"@storybook/addon-links": "^7.0.26",
|
||||
"@storybook/blocks": "^7.0.26",
|
||||
"@storybook/svelte": "^7.0.26",
|
||||
"@storybook/sveltekit": "^7.0.26",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@sveltejs/adapter-auto": "^2.1.0",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"eslint-plugin-storybook": "^0.6.12",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"storybook": "^7.0.26",
|
||||
"svelte": "^4.0.0",
|
||||
"svelte-check": "^3.4.3",
|
||||
"tslib": "2.1.0",
|
||||
"vite": "^4.3.9"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@remix-run/router": "~1.3.2",
|
||||
"@sourcegraph/branded": "workspace:*",
|
||||
"@sourcegraph/common": "workspace:*",
|
||||
"@sourcegraph/http-client": "workspace:*",
|
||||
"@sourcegraph/shared": "workspace:*",
|
||||
"@sourcegraph/web": "workspace:*",
|
||||
"@sourcegraph/wildcard": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"prismjs": "^1.29.0"
|
||||
}
|
||||
}
|
||||
|
||||
102
client/web-sveltekit/src/lib/Separator.svelte
Normal file
102
client/web-sveltekit/src/lib/Separator.svelte
Normal file
@ -0,0 +1,102 @@
|
||||
<script lang="ts" context="module">
|
||||
import { derived, type Writable } from 'svelte/store'
|
||||
|
||||
import { createLocalWritable } from '$lib/stores'
|
||||
|
||||
const dividerStore = createLocalWritable<Record<string, number>>('dividers', {})
|
||||
|
||||
export function getSeparatorPosition(name: string, defaultValue: number): Writable<number> {
|
||||
const { subscribe } = derived(dividerStore, dividers => dividers[name] ?? defaultValue)
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set(value) {
|
||||
dividerStore.update(dividers => ({ ...dividers, [name]: value }))
|
||||
},
|
||||
update(updater) {
|
||||
dividerStore.update(dividers => ({ ...dividers, [name]: updater(dividers[name]) }))
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Store to write current position (0-1) to.
|
||||
*/
|
||||
export let currentPosition: Writable<number>
|
||||
|
||||
let divider: HTMLElement | null = null
|
||||
let offset = 0
|
||||
let dragging = false
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
if (divider?.parentElement) {
|
||||
let width = (event.x - offset) / divider.parentElement.clientWidth
|
||||
if (width < 0) {
|
||||
width = 0
|
||||
} else if (width > 1) {
|
||||
width = 1
|
||||
}
|
||||
$currentPosition = width
|
||||
}
|
||||
}
|
||||
|
||||
function endResize() {
|
||||
dragging = false
|
||||
window.removeEventListener('mousemove', onMouseMove)
|
||||
window.removeEventListener('mouseup', endResize)
|
||||
}
|
||||
|
||||
function startResize(event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
if (divider?.parentElement) {
|
||||
dragging = true
|
||||
offset = divider.parentElement.getBoundingClientRect().x + divider.clientWidth
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mouseup', endResize)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- TODO: implement keyboard handlers. See https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/ -->
|
||||
<div
|
||||
bind:this={divider}
|
||||
role="separator"
|
||||
tabindex="0"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={$currentPosition}
|
||||
class:dragging
|
||||
on:mousedown={startResize}
|
||||
>
|
||||
<!-- spacer is used to increase the interactable surface-->
|
||||
<div class="spacer" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div[role='separator'] {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
width: 1px;
|
||||
margin: 0 5px;
|
||||
background-color: var(--border-color);
|
||||
cursor: col-resize;
|
||||
|
||||
.spacer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -5px;
|
||||
margin-left: -50%;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
background-color: var(--oc-blue-3);
|
||||
margin: 0 4px;
|
||||
width: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,5 +1,5 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { readable, type Readable } from 'svelte/store'
|
||||
import { readable, writable, type Readable, type Writable } from 'svelte/store'
|
||||
|
||||
import type { GraphQLClient } from '$lib/http-client'
|
||||
import type { SettingsCascade, AuthenticatedUser, TemporarySettingsStorage } from '$lib/shared'
|
||||
@ -53,3 +53,30 @@ export const graphqlClient = readable<GraphQLClient | null>(null, set => {
|
||||
// eslint-disable-next-line no-void
|
||||
void getWebGraphQLClient().then(client => set(client))
|
||||
})
|
||||
|
||||
/**
|
||||
* This store syncs the provided value with localStorage. Values must be JSON (de)seralizable.
|
||||
*/
|
||||
export function createLocalWritable<T>(localStorageKey: string, defaultValue: T): Writable<T> {
|
||||
const { subscribe, set, update } = writable(defaultValue, set => {
|
||||
const existingValue = localStorage.getItem(localStorageKey)
|
||||
if (existingValue) {
|
||||
set(JSON.parse(existingValue))
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set: value => {
|
||||
set(value)
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(value))
|
||||
},
|
||||
update: fn => {
|
||||
update(value => {
|
||||
const newValue = fn(value)
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(newValue))
|
||||
return newValue
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
import Icon from '$lib/Icon.svelte'
|
||||
import FileTree from '$lib/repo/FileTree.svelte'
|
||||
import { asStore } from '$lib/utils'
|
||||
import Separator, { getSeparatorPosition } from '$lib/Separator.svelte'
|
||||
|
||||
import type { PageData } from './$types'
|
||||
|
||||
@ -15,11 +16,14 @@
|
||||
}
|
||||
|
||||
$: treeOrError = asStore(data.treeEntries.deferred)
|
||||
|
||||
let showSidebar = true
|
||||
const sidebarSize = getSeparatorPosition('repo-sidebar', 0.2)
|
||||
$: sidebarWidth = showSidebar ? `max(200px, ${$sidebarSize * 100}%)` : undefined
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="sidebar" class:open={showSidebar}>
|
||||
<div class="sidebar" class:open={showSidebar} style:min-width={sidebarWidth} style:max-width={sidebarWidth}>
|
||||
{#if showSidebar && !$treeOrError.loading && $treeOrError.data}
|
||||
<FileTree
|
||||
activeEntry={$page.params.path ? last($page.params.path.split('/')) : ''}
|
||||
@ -39,6 +43,9 @@
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showSidebar}
|
||||
<Separator currentPosition={sidebarSize} />
|
||||
{/if}
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
18
client/web-sveltekit/src/stories/Separator.stories.ts
Normal file
18
client/web-sveltekit/src/stories/Separator.stories.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type { Meta, StoryObj } from '@storybook/svelte'
|
||||
|
||||
import Separator from '$lib/Separator.svelte'
|
||||
|
||||
import SeparatorExample from './SeparatorExample.svelte'
|
||||
|
||||
const meta: Meta<typeof SeparatorExample> = {
|
||||
component: Separator,
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const SplitPane: Story = {
|
||||
render: () => ({
|
||||
Component: SeparatorExample,
|
||||
}),
|
||||
}
|
||||
38
client/web-sveltekit/src/stories/SeparatorExample.svelte
Normal file
38
client/web-sveltekit/src/stories/SeparatorExample.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import '../routes/styles.scss'
|
||||
|
||||
import Separator, { getSeparatorPosition } from '$lib/Separator.svelte'
|
||||
|
||||
const currentPosition = getSeparatorPosition('separator-example', 0.5)
|
||||
$: width = `${$currentPosition * 100}%`
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="left" style:min-width={width} style:max-width={width}>Left content</div>
|
||||
<Separator {currentPosition} />
|
||||
<div class="right">Right content</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
section {
|
||||
// Doesn't look like border-color is set for whatever reason, making the separator invisible
|
||||
--border-color: black;
|
||||
display: flex;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
background-color: lightgray;
|
||||
}
|
||||
</style>
|
||||
@ -330,6 +330,7 @@
|
||||
"term-size": "^2.2.0",
|
||||
"terser-webpack-plugin": "^5.3.6",
|
||||
"text-table": "^0.2.0",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"typed-scss-modules": "^4.1.1",
|
||||
|
||||
1912
pnpm-lock.yaml
1912
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user