Skip to content

Commit

Permalink
feat: support code component (#1791)
Browse files Browse the repository at this point in the history
* feat: support code component

* feat: using shiki

* types: fix code type

* feat(code): optimize logic based on review comment

* feat(code): support highlighter-provider

* feat(code): fix test cases

* feat(code): optimize docs based on review comment

* feat(code): optimize according to review comments
  • Loading branch information
cszhjh authored Oct 9, 2024
1 parent e816d9f commit fefbde6
Show file tree
Hide file tree
Showing 34 changed files with 995 additions and 7 deletions.
15 changes: 8 additions & 7 deletions packages/varlet-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,21 @@
"decimal.js": "^10.2.1"
},
"devDependencies": {
"@types/node": "^18.7.18",
"@varlet/cli": "workspace:*",
"@varlet/ui": "workspace: *",
"@varlet/touch-emulator": "workspace:*",
"@varlet/ui": "workspace: *",
"@vitest/coverage-istanbul": "2.0.5",
"@vue/runtime-core": "3.4.21",
"@vue/test-utils": "2.4.1",
"@types/node": "^18.7.18",
"@vitest/coverage-istanbul": "2.0.5",
"zod": "^3.23.8",
"jsdom": "24.1.1",
"vitest": "2.0.5",
"clipboard": "^2.0.6",
"jsdom": "24.1.1",
"live-server": "^1.2.1",
"shiki": "^1.21.0",
"typescript": "^5.1.5",
"vitest": "2.0.5",
"vue": "3.4.21",
"vue-router": "4.2.0"
"vue-router": "4.2.0",
"zod": "^3.23.8"
}
}
43 changes: 43 additions & 0 deletions packages/varlet-ui/src/code/Code.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div :class="n()" v-html="highlightedCode"></div>
</template>

<script lang="ts">
import { computed, defineComponent, ref, watch } from 'vue'
import { createNamespace } from '../utils/components'
import { props } from './props'
import { injectHighlighterProvider } from '../highlighter-provider/provide'
import { isFunction } from '@varlet/shared'
const { name, n } = createNamespace('code')
export default defineComponent({
name,
props,
setup(props) {
const { highlighter, theme } = injectHighlighterProvider()
const highlightedCode = ref<string | undefined>(props.code)
const getTheme = computed(() => props.theme || theme)
isFunction(highlighter?.codeToHtml) &&
watch(
() => [props.code, props.language, getTheme.value],
async ([code, lang, theme]) => {
highlightedCode.value = await highlighter.codeToHtml(code || '', { lang, theme })
},
{ immediate: true }
)
return {
highlightedCode,
n,
}
},
})
</script>

<style lang="less">
@import '../styles/common';
@import './code';
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`test code component props > test code content 1`] = `
"<div class="var-highlighter-provider">
<div class="var-code"><pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">function</span><span style="color:#A6E22E"> twoSum</span><span style="color:#F8F8F2">(</span><span style="color:#FD971F;font-style:italic">nums</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">target</span><span style="color:#F8F8F2">) {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> map </span><span style="color:#F92672">=</span><span style="color:#F92672"> new</span><span style="color:#A6E22E"> Map</span><span style="color:#F8F8F2">();</span></span>
<span class="line"><span style="color:#F92672"> for</span><span style="color:#F8F8F2"> (</span><span style="color:#66D9EF;font-style:italic">let</span><span style="color:#F8F8F2"> i </span><span style="color:#F92672">=</span><span style="color:#AE81FF"> 0</span><span style="color:#F8F8F2">; i </span><span style="color:#F92672">&lt;</span><span style="color:#F8F8F2"> nums.length; i</span><span style="color:#F92672">++</span><span style="color:#F8F8F2">) {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> num </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> nums[i];</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> theOther </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> target </span><span style="color:#F92672">-</span><span style="color:#F8F8F2"> num;</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (map.</span><span style="color:#A6E22E">has</span><span style="color:#F8F8F2">(theOther)) {</span></span>
<span class="line"><span style="color:#F92672"> return</span><span style="color:#F8F8F2"> [map.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(theOther), i];</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2"> map.</span><span style="color:#A6E22E">set</span><span style="color:#F8F8F2">(num, i);</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2">};</span></span></code></pre>
</div>
</div>"
`;
exports[`test code component props > test code lang 1`] = `
"<div class="var-highlighter-provider">
<div class="var-code" content="function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
const theOther = target - num;
if (map.has(theOther)) {
return [map.get(theOther), i];
}
map.set(num, i);
}
};" lang="javascript"><pre class="shiki nord" style="background-color:#2e3440ff;color:#d8dee9ff" tabindex="0"><code><span class="line"><span></span></span></code></pre>
</div>
</div>"
`;
exports[`test code component props > test code theme 1`] = `
"<div class="var-highlighter-provider">
<div class="var-code"><pre class="shiki material-theme" style="background-color:#263238;color:#EEFFFF" tabindex="0"><code><span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF"> twoSum</span><span style="color:#89DDFF">(</span><span style="color:#EEFFFF;font-style:italic">nums</span><span style="color:#89DDFF">,</span><span style="color:#EEFFFF;font-style:italic"> target</span><span style="color:#89DDFF">)</span><span style="color:#89DDFF"> {</span></span>
<span class="line"><span style="color:#C792EA"> const</span><span style="color:#EEFFFF"> map</span><span style="color:#89DDFF"> =</span><span style="color:#89DDFF"> new</span><span style="color:#82AAFF"> Map</span><span style="color:#F07178">()</span><span style="color:#89DDFF">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic"> for</span><span style="color:#F07178"> (</span><span style="color:#C792EA">let</span><span style="color:#EEFFFF"> i</span><span style="color:#89DDFF"> =</span><span style="color:#F78C6C"> 0</span><span style="color:#89DDFF">;</span><span style="color:#EEFFFF"> i</span><span style="color:#89DDFF"> &lt;</span><span style="color:#EEFFFF"> nums</span><span style="color:#89DDFF">.</span><span style="color:#EEFFFF">length</span><span style="color:#89DDFF">;</span><span style="color:#EEFFFF"> i</span><span style="color:#89DDFF">++</span><span style="color:#F07178">) </span><span style="color:#89DDFF">{</span></span>
<span class="line"><span style="color:#C792EA"> const</span><span style="color:#EEFFFF"> num</span><span style="color:#89DDFF"> =</span><span style="color:#EEFFFF"> nums</span><span style="color:#F07178">[</span><span style="color:#EEFFFF">i</span><span style="color:#F07178">]</span><span style="color:#89DDFF">;</span></span>
<span class="line"><span style="color:#C792EA"> const</span><span style="color:#EEFFFF"> theOther</span><span style="color:#89DDFF"> =</span><span style="color:#EEFFFF"> target</span><span style="color:#89DDFF"> -</span><span style="color:#EEFFFF"> num</span><span style="color:#89DDFF">;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic"> if</span><span style="color:#F07178"> (</span><span style="color:#EEFFFF">map</span><span style="color:#89DDFF">.</span><span style="color:#82AAFF">has</span><span style="color:#F07178">(</span><span style="color:#EEFFFF">theOther</span><span style="color:#F07178">)) </span><span style="color:#89DDFF">{</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic"> return</span><span style="color:#F07178"> [</span><span style="color:#EEFFFF">map</span><span style="color:#89DDFF">.</span><span style="color:#82AAFF">get</span><span style="color:#F07178">(</span><span style="color:#EEFFFF">theOther</span><span style="color:#F07178">)</span><span style="color:#89DDFF">,</span><span style="color:#EEFFFF"> i</span><span style="color:#F07178">]</span><span style="color:#89DDFF">;</span></span>
<span class="line"><span style="color:#89DDFF"> }</span></span>
<span class="line"><span style="color:#EEFFFF"> map</span><span style="color:#89DDFF">.</span><span style="color:#82AAFF">set</span><span style="color:#F07178">(</span><span style="color:#EEFFFF">num</span><span style="color:#89DDFF">,</span><span style="color:#EEFFFF"> i</span><span style="color:#F07178">)</span><span style="color:#89DDFF">;</span></span>
<span class="line"><span style="color:#89DDFF"> }</span></span>
<span class="line"><span style="color:#89DDFF">};</span></span></code></pre>
</div>
</div>"
`;
92 changes: 92 additions & 0 deletions packages/varlet-ui/src/code/__tests__/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Code from '..'
import VarCode from '../Code'
import VarHighlighterProvider from '../../highlighter-provider'
import { mount } from '@vue/test-utils'
import { createApp, h } from 'vue'
import { delay } from '../../utils/test'
import { expect, describe, test } from 'vitest'
import { codeToHtml } from 'shiki'

const code = `function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
const theOther = target - num;
if (map.has(theOther)) {
return [map.get(theOther), i];
}
map.set(num, i);
}
};`

test('test code use', () => {
const app = createApp({}).use(Code)
expect(app.component(Code.name)).toBeTruthy()
})

describe('test code component props', () => {
test('test code content', async () => {
const wrapper = mount(VarHighlighterProvider, {
props: {
highlighter: {
codeToHtml,
},
},
slots: {
default: () =>
h(VarCode, {
code,
language: 'javascript',
theme: 'monokai',
}),
},
})

await delay(300)
expect(wrapper.html()).toMatchSnapshot()
wrapper.unmount()
})

test('test code lang', async () => {
const wrapper = mount(VarHighlighterProvider, {
props: {
highlighter: {
codeToHtml,
},
},
slots: {
default: () =>
h(VarCode, {
content: code,
lang: 'javascript',
}),
},
})

await delay(300)
expect(wrapper.html()).toMatchSnapshot()
wrapper.unmount()
})

test('test code theme', async () => {
const wrapper = mount(VarHighlighterProvider, {
props: {
highlighter: {
codeToHtml,
},
},
slots: {
default: () =>
h(VarCode, {
code,
language: 'javascript',
theme: 'material-theme',
}),
},
})

await delay(300)
expect(wrapper.html()).toMatchSnapshot()
wrapper.unmount()
})
})
21 changes: 21 additions & 0 deletions packages/varlet-ui/src/code/code.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
:root {
--code-border-radius: 4px;
--code-content-padding: 16px;
}

.var-code {
border-radius: var(--code-border-radius);
position: relative;
overflow: hidden;
width: 100%;
height: 100%;

pre {
padding: var(--code-content-padding);
border-radius: var(--code-border-radius);
width: 100%;
height: 100%;
margin: 0;
overflow: auto;
}
}
83 changes: 83 additions & 0 deletions packages/varlet-ui/src/code/docs/en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Code

### Intro

Code component, used to display code blocks and highlight code syntax.

### Notes

- Due to package size considerations, Varlet does not include a built-in code highlighter. If you need to use the code block component, please ensure you use the HighlighterProvider component to customize the highlighter.
- Using the HighlighterProvider component, you can set different highlighters for different code blocks. It is recommended to use [Shiki](https://shiki.style/) as the highlighter, as it has built-in support for `codeToHtml` and offers more flexibility in switching languages and themes.

### Basic Usage

```html
<script setup>
import { codeToHtml } from 'shiki'
function createHighlighter() {
return {
codeToHtml,
}
}
</script>

<template>
<var-highlighter-provider :highlighter="createHighlighter()" theme="nord">
<var-code code="console.log('varlet')" language="javascript" />
<var-code code="console.log('varlet')" language="javascript" theme='one-dark' />
<var-code code="console.log('varlet')" language="javascript" theme='one-dark-pro' />
</var-highlighter-provider>
</template>
```

## API

### Props

#### Code Props

| Prop | Description | Type | Default |
|------------------|--------------------------------------------------------------|----------------|------------------|
| `code` | Code Snippet | _string_ | `-` |
| `language` | Language | _string_ | `-` |
| `theme` | Theme | _string_ | `-` |

#### HighlighterProvider Props

| Prop | Description | Type | Default |
|------------------|--------------------------------------------------------------|----------------|------------------|
| `highlighter` | Shader | `Highlighter` | `-` |
| `theme` | Theme | _string_ | `-` |
| `tag` | Tag name | _string_ | `div` |


#### Highlighter

| Prop | Description | Type | Default |
| ------ | ------ | ------ | ------ |
| `codeToHtml` | Callback this function when the content, theme, or language changes, and specify the lang and theme options. It will return an HTML string. | `(code: string, options: CodeToHtmlOptions) => Promise<string>` | `-`

#### CodeToHtmlOptions

| Prop | Description | Type | Default |
| ------ | ------ | ------ | ------ |
| `lang` | language | _string_ | `-` |
| `theme` | theme | _string_ | `-` |

### Slots

#### HighlighterProvider Slots

| Name | Description | SlotProps |
| --- | --- | --- |
| `default` | Component content | `-` |

### Style Variables

Here are the CSS variables used by the component. Styles can be customized using [StyleProvider](#/en-US/style-provider).

| Variable | Default |
| --- | --- |
| `--code-border-radius` | `4px` |
| `--code-content-padding` | `16px` |
Loading

0 comments on commit fefbde6

Please sign in to comment.