@hyvor/design 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,8 @@
2
2
  import Button from '../Button/Button.svelte';
3
3
  import IconCopy from '@hyvor/icons/IconCopy';
4
4
  import toast from '../Toast/toast.js';
5
- import { getCode, sanitizeLines, type Language } from './getCode.js';
5
+ import { highlightCode, sanitizeLines } from './getCode.js';
6
+ import type { Language } from './types.codeblock.js';
6
7
 
7
8
  interface Props {
8
9
  code: string;
@@ -19,22 +20,22 @@
19
20
  }
20
21
  </script>
21
22
 
22
- <div class="code-container">
23
+ <div class="hds-code-block">
23
24
  <div class="copy-button">
24
25
  <Button color="input" size="x-small" onclick={copyToClipboard}>
25
26
  <IconCopy size={10} />
26
27
  </Button>
27
28
  </div>
28
- {@html await getCode(sanitizedCode, language)}
29
+ {@html await highlightCode(sanitizedCode, language)}
29
30
  </div>
30
31
 
31
32
  <style>
32
33
  /*styles for CodeBlock component */
33
- .code-container {
34
+ .hds-code-block {
34
35
  position: relative;
35
36
  }
36
37
 
37
- .code-container .copy-button {
38
+ .copy-button {
38
39
  position: absolute;
39
40
  top: 0px;
40
41
  right: 12px;
@@ -45,33 +46,4 @@
45
46
  .copy-button :global(button) {
46
47
  border: 1px solid var(--border);
47
48
  }
48
-
49
- .code-container :global(pre) {
50
- text-align: left;
51
- white-space: pre;
52
- word-spacing: normal;
53
- word-break: normal;
54
- word-wrap: normal;
55
- overflow: auto;
56
- border-radius: 20px;
57
- padding: 20px;
58
- line-height: 1.2;
59
- background-color: #f4f2f0 !important;
60
- }
61
-
62
- .code-container :global(pre code) {
63
- all: unset;
64
- font-size: 14px;
65
- line-height: 1.5 !important;
66
- tab-size: 4;
67
- hyphens: none;
68
- }
69
-
70
- :global(:root.dark .shiki, :root.dark .shiki span) {
71
- color: var(--shiki-dark) !important;
72
- background-color: var(--shiki-dark-bg) !important;
73
- font-style: var(--shiki-dark-font-style) !important;
74
- font-weight: var(--shiki-dark-font-weight) !important;
75
- text-decoration: var(--shiki-dark-text-decoration) !important;
76
- }
77
49
  </style>
@@ -1,4 +1,4 @@
1
- import { type Language } from './getCode.js';
1
+ import type { Language } from './types.codeblock.js';
2
2
  interface Props {
3
3
  code: string;
4
4
  language?: Language | null;
@@ -1,3 +1,11 @@
1
- export type Language = 'html' | 'css' | 'js' | 'ts' | 'yaml' | 'json' | 'svelte' | 'jsx' | 'php' | 'sh' | string;
2
- export declare function sanitizeLines(code: string): string;
3
- export declare function getCode(code: string, language: Language | null): Promise<string>;
1
+ /**
2
+ * @param {string} code
3
+ * @returns {string}
4
+ */
5
+ export function sanitizeLines(code: string): string;
6
+ /**
7
+ * @param {string} code
8
+ * @param {import('./types.codeblock.ts').Language | null} language
9
+ * @returns {Promise<string>}
10
+ */
11
+ export function highlightCode(code: string, language: import("./types.codeblock.ts").Language | null): Promise<string>;
@@ -1,33 +1,55 @@
1
1
  import { codeToHtml } from 'shiki';
2
+
3
+ /**
4
+ * @param {string} code
5
+ * @returns {string}
6
+ */
2
7
  export function sanitizeLines(code) {
3
- let ret = code;
4
- // remove the first empty line
5
- ret = ret.replace(/^[^\S\r\n]*\n/, '');
6
- // remove the last empty line
7
- ret = ret.replace(/\n[^\S\r\n]*$/, '');
8
- let lines = ret.split('\n');
9
- let indent = null; // number of spaces to remove from each line
10
- lines = lines.map((line) => {
11
- if (indent === null) {
12
- // find the indent
13
- const match = line.match(/^(\s*)/);
14
- indent = match ? match[1].length : 0;
15
- }
16
- if (line.substring(0, indent).trim() !== '') {
17
- return line;
18
- }
19
- // remove the indent
20
- line = line.substring(indent);
21
- return line;
22
- });
23
- return lines.join('\n');
8
+ let ret = code;
9
+
10
+ // remove the first empty line
11
+ ret = ret.replace(/^[^\S\r\n]*\n/, '');
12
+ // remove the last empty line
13
+ ret = ret.replace(/\n[^\S\r\n]*$/, '');
14
+
15
+ let lines = ret.split('\n');
16
+
17
+ /**
18
+ * @type {null | number}
19
+ */
20
+ let indent = null; // number of spaces to remove from each line
21
+
22
+ lines = lines.map((line) => {
23
+ if (indent === null) {
24
+ // find the indent
25
+ const match = line.match(/^(\s*)/);
26
+ indent = match ? match[1].length : 0;
27
+ }
28
+
29
+ if (line.substring(0, indent).trim() !== '') {
30
+ return line;
31
+ }
32
+
33
+ // remove the indent
34
+ line = line.substring(indent);
35
+
36
+ return line;
37
+ });
38
+
39
+ return lines.join('\n');
24
40
  }
25
- export async function getCode(code, language) {
26
- return await codeToHtml(code, {
27
- lang: language || 'text',
28
- themes: {
29
- light: 'vitesse-light',
30
- dark: 'nord'
31
- }
32
- });
41
+
42
+ /**
43
+ * @param {string} code
44
+ * @param {import('./types.codeblock.ts').Language | null} language
45
+ * @returns {Promise<string>}
46
+ */
47
+ export async function highlightCode(code, language) {
48
+ return await codeToHtml(code, {
49
+ lang: language || 'text',
50
+ themes: {
51
+ light: 'vitesse-light',
52
+ dark: 'nord'
53
+ }
54
+ });
33
55
  }
@@ -0,0 +1 @@
1
+ export type Language = 'html' | 'css' | 'js' | 'ts' | 'yaml' | 'json' | 'svelte' | 'jsx' | 'php' | 'sh' | string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,17 +1,49 @@
1
1
  <script lang="ts">
2
2
  import { setContext } from 'svelte';
3
+ import type { TabNavState } from './tabnav.js';
3
4
 
4
- interface Props {
5
- basePath?: string;
5
+ type Props = (
6
+ | {
7
+ basePath?: undefined;
8
+ pathname?: undefined;
9
+ goto?: undefined;
10
+ }
11
+ | {
12
+ basePath: string;
13
+ // SvelteKit's `page.url.pathname`, e.g. `import { page } from '$app/state'`.
14
+ pathname: string;
15
+ // SvelteKit's `goto`, e.g. `import { goto } from '$app/navigation'`.
16
+ goto: (url: string, opts?: { replaceState?: boolean }) => void;
17
+ }
18
+ ) & {
6
19
  replaceState?: boolean;
7
20
  children?: import('svelte').Snippet;
8
- }
21
+ };
22
+
23
+ let { children, basePath, replaceState = false, pathname: pathnameProp, goto }: Props = $props();
24
+
25
+ let fallbackPathname = $state(typeof window !== 'undefined' ? window.location.pathname : '');
26
+
27
+ $effect(() => {
28
+ if (!basePath || pathnameProp !== undefined) return;
9
29
 
10
- let { children, basePath, replaceState = false }: Props = $props();
30
+ const onPopState = () => {
31
+ fallbackPathname = window.location.pathname;
32
+ };
33
+ window.addEventListener('popstate', onPopState);
34
+ return () => window.removeEventListener('popstate', onPopState);
35
+ });
11
36
 
12
- const tabNavState = {
37
+ const tabNavState: TabNavState = {
13
38
  basePath,
14
- replaceState
39
+ replaceState,
40
+ goto,
41
+ get pathname() {
42
+ return pathnameProp ?? fallbackPathname;
43
+ },
44
+ set pathname(value: string) {
45
+ fallbackPathname = value;
46
+ }
15
47
  };
16
48
 
17
49
  setContext('tab-nav-state', tabNavState);
@@ -1,8 +1,17 @@
1
- interface Props {
2
- basePath?: string;
1
+ type Props = ({
2
+ basePath?: undefined;
3
+ pathname?: undefined;
4
+ goto?: undefined;
5
+ } | {
6
+ basePath: string;
7
+ pathname: string;
8
+ goto: (url: string, opts?: {
9
+ replaceState?: boolean;
10
+ }) => void;
11
+ }) & {
3
12
  replaceState?: boolean;
4
13
  children?: import('svelte').Snippet;
5
- }
14
+ };
6
15
  declare const TabNav: import("svelte").Component<Props, {}, "">;
7
16
  type TabNav = ReturnType<typeof TabNav>;
8
17
  export default TabNav;
@@ -1,7 +1,5 @@
1
1
  <script lang="ts">
2
2
  import { getContext } from 'svelte';
3
- import { page } from '$app/state';
4
- import { goto } from '$app/navigation';
5
3
  import type { TabNavState } from './tabnav.js';
6
4
 
7
5
  interface Props {
@@ -28,8 +26,7 @@
28
26
  if (active) return true;
29
27
 
30
28
  if (tabNavState.basePath) {
31
- const currentUrl = page.url.pathname;
32
- return currentUrl === getTabPath();
29
+ return tabNavState.pathname === getTabPath();
33
30
  }
34
31
 
35
32
  return false;
@@ -37,7 +34,14 @@
37
34
 
38
35
  function handleClick(event: MouseEvent) {
39
36
  if (tabNavState.basePath) {
40
- goto(getTabPath());
37
+ const path = getTabPath();
38
+
39
+ if (tabNavState.goto) {
40
+ tabNavState.goto(path, { replaceState: tabNavState.replaceState });
41
+ tabNavState.pathname = path;
42
+ } else {
43
+ window.location.href = path;
44
+ }
41
45
  }
42
46
  onclick?.(event);
43
47
  }
@@ -1,4 +1,8 @@
1
1
  export interface TabNavState {
2
2
  basePath: string | undefined;
3
3
  replaceState: boolean;
4
+ pathname: string;
5
+ goto?: (url: string, opts?: {
6
+ replaceState?: boolean;
7
+ }) => void;
4
8
  }
@@ -12,6 +12,8 @@ export { default as BoxShadowPicker } from './BoxShadowPicker/BoxShadowPicker.sv
12
12
  export { default as Callout } from './Callout/Callout.svelte';
13
13
  export { default as Checkbox } from './Checkbox/Checkbox.svelte';
14
14
  export { default as CodeBlock } from './CodeBlock/CodeBlock.svelte';
15
+ export { highlightCode } from './CodeBlock/getCode.js';
16
+ export { markdownPlugin } from './markdown/markdown.js';
15
17
  export { default as TabbedCodeBlock } from './CodeBlock/TabbedCodeBlock.svelte';
16
18
  export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
17
19
  export { default as ConsoleLoader } from './ConsoleLoader/ConsoleLoader.svelte';
@@ -12,6 +12,8 @@ export { default as BoxShadowPicker } from './BoxShadowPicker/BoxShadowPicker.sv
12
12
  export { default as Callout } from './Callout/Callout.svelte';
13
13
  export { default as Checkbox } from './Checkbox/Checkbox.svelte';
14
14
  export { default as CodeBlock } from './CodeBlock/CodeBlock.svelte';
15
+ export { highlightCode } from './CodeBlock/getCode.js';
16
+ export { markdownPlugin } from './markdown/markdown.js';
15
17
  export { default as TabbedCodeBlock } from './CodeBlock/TabbedCodeBlock.svelte';
16
18
  export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
17
19
  export { default as ConsoleLoader } from './ConsoleLoader/ConsoleLoader.svelte';
@@ -0,0 +1,17 @@
1
+ import { mdsvex, escapeSvelte } from 'mdsvex';
2
+ import { highlightCode } from '../CodeBlock/getCode.js';
3
+
4
+ // this is a JS file because we can use it in svelte.config.js locally
5
+
6
+ export function markdownPlugin() {
7
+ return mdsvex({
8
+ extensions: ['.md'],
9
+ highlight: {
10
+ highlighter: async (code, lang) => {
11
+ const highlitedCode = await highlightCode(code, lang || null);
12
+ const html = `<div class="hds-code-block">${highlitedCode}</div>`;
13
+ return escapeSvelte(html);
14
+ }
15
+ }
16
+ });
17
+ }
package/dist/index.css CHANGED
@@ -61,3 +61,34 @@ a {
61
61
  color: var(--link);
62
62
  text-decoration: underline;
63
63
  }
64
+
65
+ /** code block styles */
66
+ .hds-code-block pre {
67
+ text-align: left;
68
+ white-space: pre;
69
+ word-spacing: normal;
70
+ word-break: normal;
71
+ word-wrap: normal;
72
+ overflow: auto;
73
+ border-radius: 20px;
74
+ padding: 20px;
75
+ line-height: 1.2;
76
+ background-color: #f4f2f0 !important;
77
+ }
78
+
79
+ .hds-code-block pre code {
80
+ all: unset;
81
+ font-size: 14px;
82
+ line-height: 1.5 !important;
83
+ tab-size: 4;
84
+ hyphens: none;
85
+ }
86
+
87
+ :root.dark .shiki,
88
+ :root.dark .shiki span {
89
+ color: var(--shiki-dark) !important;
90
+ background-color: var(--shiki-dark-bg) !important;
91
+ font-style: var(--shiki-dark-font-style) !important;
92
+ font-weight: var(--shiki-dark-font-weight) !important;
93
+ text-decoration: var(--shiki-dark-text-decoration) !important;
94
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyvor/design",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "repository": {
@@ -65,6 +65,7 @@
65
65
  "deepmerge-ts": "^7.1.5",
66
66
  "emojibase-data": "^17.0.0",
67
67
  "intl-messageformat": "^11.1.2",
68
+ "mdsvex": "^0.12.8",
68
69
  "shiki": "^3.22.0",
69
70
  "svelte-awesome-color-picker": "^4.1.1",
70
71
  "tocbot": "^4.36.4"