Skip to content

Commit

Permalink
feat: support line number (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored Jan 19, 2025
1 parent b37c912 commit d49d4a1
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 192 deletions.
4 changes: 1 addition & 3 deletions site/app/editor-example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export default function Page() {

export function EditorExample({ searchParams } : { searchParams: { c?: string } }) {
return (
<div>
<LiveEditor searchParams={searchParams} defaultCode={defaultCode} />
</div>
<LiveEditor searchParams={searchParams} defaultCode={defaultCode} />
)
}
28 changes: 20 additions & 8 deletions site/app/live-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { highlight } from 'sugar-high'

const CODE_QUERY_KEY = 'c'

function ControlButton({ id, checked, onChange, name }) {
function ControlButton({ id, checked, onChange, propName }: {
id: string
checked: boolean
onChange: (checked: boolean) => void
propName: string
}) {
return (
<span className='control-button'>
<input
Expand All @@ -16,7 +21,7 @@ function ControlButton({ id, checked, onChange, name }) {
onChange={(event) => onChange(event.target.checked)}
/>
<label data-checked={checked} htmlFor={id}>
{`${name}={`}<span>{checked ? '●' : '○'}</span>{`}`}
{`${propName}={`}<span>{checked ? '●' : '○'}</span>{`}`}
</label>
</span>
)
Expand All @@ -34,23 +39,29 @@ export function LiveEditor({
const [code, setCode] = useState(initialCode)
const [title, setTitle] = useState<'index.js' | ''>('index.js')
const [controls, setControls] = useState(true)
const [lineNumbers, setLineNumbers] = useState(true)

return (
<div>
{/* Controls to for displaying the `title` and `controls` */}
{/* Controls to for displaying the `title`, `controls`, `lineNumbers` */}
<div className="controls-manager">
<ControlButton
id="title-control"
id="control-title"
checked={!!title}
onChange={(checked) => setTitle(checked ? 'index.js' : '')}
name="title"
propName="title"
/>

<ControlButton
id="controls-control"
id="control-control"
checked={controls}
onChange={setControls}
name="controls"
propName="controls"
/>
<ControlButton
id="control-line-numbers"
checked={lineNumbers}
onChange={setLineNumbers}
propName="lineNumbers"
/>
</div>

Expand All @@ -59,6 +70,7 @@ export function LiveEditor({
className="editor"
title={title}
controls={controls}
lineNumbers={lineNumbers}
highlight={(text) => highlight(text)}
onChange={(text) => setCode(text)}
/>
Expand Down
9 changes: 9 additions & 0 deletions site/app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ input[type=radio] {
border: 1px solid rgba(163, 169, 165, 0.2);
transition: border 0.2s ease-in-out;
}
.editor[data-codice-editor] textarea:focus {
border: 1px solid hsla(137, 100.00%, 94.30%, 0.30);
}
.editor[data-codice-editor-line-numbers="false"] [data-codice-editor-content] {
padding-left: 0;
}
.editor[data-codice-editor-line-numbers="true"] [data-codice-editor-content] {
padding-left: 16px;
}
[data-codice-editor-title] {
color: hsla(0, 0%, 87%, 0.34);
}
Expand Down
74 changes: 23 additions & 51 deletions src/code/code.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,139 +5,111 @@ import { renderToString } from 'react-dom/server'
describe('Code', () => {
it('default props', () => {
expect(renderToString(<Code>test</Code>)).toMatchInlineSnapshot(`
"<div data-codice-code="true"><style>
"<div data-codice-code="true"><style data-codice-style="true">[data-codice-code] {
--codice-editor-line-number-color: #a4a4a4;
}
[data-codice-code] pre {
white-space: pre-wrap;
margin: 0;
}
[data-codice-code] code {
counter-reset: sh-line-number;
border: none;
}
[data-codice-editor-header] {
position: relative;
display: flex;
padding: 16px 22px 8px;
align-items: center;
}
[data-codice-editor-title] {
display: inline-block;
flex: 1 0;
text-align: center;
line-height: 1;
}
[data-codice-editor-controls] {
display: inline-flex;
align-self: center;
justify-self: start;
align-items: center;
justify-content: center;
}
[data-codice-editor-controls],
[data-codice-editor-controls-placeholder] {
width: 52px;
}
[data-codice-editor-control] {
display: flex;
width: 10px;
height: 10px;
margin: 3px;
border-radius: 50%;
background-color: var(--codice-editor-control-color);
}
</style><pre data-codice-code-content="true"><code>test</code></pre></div>"
`)
})

it('with title', () => {
expect(renderToString(<Code title="file.js">test</Code>)).toMatchInlineSnapshot(`
"<div data-codice-code="true"><style>
"<div data-codice-code="true"><style data-codice-style="true">[data-codice-code] {
--codice-editor-line-number-color: #a4a4a4;
}
[data-codice-code] pre {
white-space: pre-wrap;
margin: 0;
}
[data-codice-code] code {
counter-reset: sh-line-number;
border: none;
}
[data-codice-editor-header] {
</style><div data-codice-editor-header="true"><style data-codice-style="true">[data-codice-editor-header] {
position: relative;
display: flex;
padding: 16px 22px 8px;
align-items: center;
}
[data-codice-editor-title] {
[data-codice-editor-header] [data-codice-editor-title] {
display: inline-block;
flex: 1 0;
text-align: center;
line-height: 1;
}
[data-codice-editor-controls] {
[data-codice-editor-header] [data-codice-editor-controls] {
display: inline-flex;
align-self: center;
justify-self: start;
align-items: center;
justify-content: center;
}
[data-codice-editor-controls],
[data-codice-editor-controls-placeholder] {
[data-codice-editor-header] [data-codice-editor-controls],
[data-codice-editor-header] [data-codice-editor-controls-placeholder] {
width: 52px;
}
[data-codice-editor-control] {
[data-codice-editor-header] [data-codice-editor-control] {
display: flex;
width: 10px;
height: 10px;
margin: 3px;
border-radius: 50%;
background-color: var(--codice-editor-control-color);
}
</style><div data-codice-editor-header="true"><div data-codice-editor-title="true">file.js</div></div><pre data-codice-code-content="true"><code>test</code></pre></div>"
</style><div data-codice-editor-title="true">file.js</div></div><pre data-codice-code-content="true"><code>test</code></pre></div>"
`)
})

it('with controls', () => {
expect(renderToString(<Code controls>test</Code>)).toMatchInlineSnapshot(`
"<div data-codice-code="true"><style>
"<div data-codice-code="true"><style data-codice-style="true">[data-codice-code] {
--codice-editor-line-number-color: #a4a4a4;
}
[data-codice-code] pre {
white-space: pre-wrap;
margin: 0;
}
[data-codice-code] code {
counter-reset: sh-line-number;
border: none;
}
[data-codice-editor-header] {
</style><div data-codice-editor-header="true"><style data-codice-style="true">[data-codice-editor-header] {
position: relative;
display: flex;
padding: 16px 22px 8px;
align-items: center;
}
[data-codice-editor-title] {
[data-codice-editor-header] [data-codice-editor-title] {
display: inline-block;
flex: 1 0;
text-align: center;
line-height: 1;
}
[data-codice-editor-controls] {
[data-codice-editor-header] [data-codice-editor-controls] {
display: inline-flex;
align-self: center;
justify-self: start;
align-items: center;
justify-content: center;
}
[data-codice-editor-controls],
[data-codice-editor-controls-placeholder] {
[data-codice-editor-header] [data-codice-editor-controls],
[data-codice-editor-header] [data-codice-editor-controls-placeholder] {
width: 52px;
}
[data-codice-editor-control] {
[data-codice-editor-header] [data-codice-editor-control] {
display: flex;
width: 10px;
height: 10px;
margin: 3px;
border-radius: 50%;
background-color: var(--codice-editor-control-color);
}
</style><div data-codice-editor-header="true"><div data-codice-editor-controls="true"><span data-codice-editor-control="true"></span><span data-codice-editor-control="true"></span><span data-codice-editor-control="true"></span></div><span data-codice-editor-controls-placeholder="true"></span></div><pre data-codice-code-content="true"><code>test</code></pre></div>"
</style><div data-codice-editor-controls="true"><span data-codice-editor-control="true"></span><span data-codice-editor-control="true"></span><span data-codice-editor-control="true"></span></div><span data-codice-editor-controls-placeholder="true"></span></div><pre data-codice-code-content="true"><code>test</code></pre></div>"
`)
})
})
11 changes: 9 additions & 2 deletions src/code/code.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { css } from './css'
import { baseCss, headerCss, lineNumbersCss } from './css'

export function CodeHeader({ title, controls = false }: { title?: string; controls: boolean }) {
if (!title && !controls) return null
// TODO: migrate inline css
return (
<div data-codice-editor-header>
<style data-codice-style>{headerCss}</style>
{controls ? (
<div data-codice-editor-controls>
<span data-codice-editor-control />
Expand All @@ -23,17 +24,23 @@ export function Code({
title,
controls,
preformatted = true,
lineNumbers = false,
...props
}: {
children: string
/** Whether to use a preformatted block <pre><code> */
preformatted?: boolean
title?: string
controls?: boolean
lineNumbers?: boolean
} & React.HTMLAttributes<HTMLDivElement>) {
const css = baseCss + (lineNumbers ? lineNumbersCss : '')

return (
<div {...props} data-codice-code>
<style key='codice-code-style'>{css}</style>
<style data-codice-style>
{css}
</style>
<CodeHeader title={title} controls={controls} />
{preformatted ? (
<pre data-codice-code-content>
Expand Down
44 changes: 34 additions & 10 deletions src/code/css.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,65 @@
export const css = `
[data-codice-code] pre {
const C = `[data-codice-code]`
const H = `[data-codice-editor-header]`

export const baseCss = `\
${C} {
--codice-editor-line-number-color: #a4a4a4;
}
${C} pre {
white-space: pre-wrap;
margin: 0;
}
[data-codice-code] code {
counter-reset: sh-line-number;
${C} code {
border: none;
}
[data-codice-editor-header] {
`

export const headerCss = `\
${H} {
position: relative;
display: flex;
padding: 16px 22px 8px;
align-items: center;
}
[data-codice-editor-title] {
${H} [data-codice-editor-title] {
display: inline-block;
flex: 1 0;
text-align: center;
line-height: 1;
}
[data-codice-editor-controls] {
${H} [data-codice-editor-controls] {
display: inline-flex;
align-self: center;
justify-self: start;
align-items: center;
justify-content: center;
}
[data-codice-editor-controls],
[data-codice-editor-controls-placeholder] {
${H} [data-codice-editor-controls],
${H} [data-codice-editor-controls-placeholder] {
width: 52px;
}
[data-codice-editor-control] {
${H} [data-codice-editor-control] {
display: flex;
width: 10px;
height: 10px;
margin: 3px;
border-radius: 50%;
background-color: var(--codice-editor-control-color);
}
`

export const lineNumbersCss = `\
@scope {
code { counter-reset: codice-code-line-number; }
.sh__line::before {
counter-increment: codice-code-line-number 1;
content: counter(codice-code-line-number);
display: inline-block;
width: 24px;
margin-right: 18px;
margin-left: -42px;
text-align: right;
color: var(--codice-editor-line-number-color);
}
}
`
Loading

0 comments on commit d49d4a1

Please sign in to comment.