├── readme.md ├── .prettierrc ├── .vscode └── settings.json ├── web ├── app │ ├── recipes │ │ ├── .prettierrc.json │ │ ├── diff │ │ │ ├── demo.js │ │ │ ├── diff.js │ │ │ ├── usage.mdx │ │ │ ├── page.js │ │ │ └── extension.js │ │ ├── link │ │ │ ├── extension.js │ │ │ ├── demo.js │ │ │ └── page.js │ │ ├── collapse │ │ │ ├── extension.js │ │ │ ├── demo.js │ │ │ ├── page.js │ │ │ └── collapse.js │ │ ├── focus │ │ │ ├── demo.js │ │ │ ├── page.js │ │ │ └── extension.js │ │ ├── tabs │ │ │ ├── tabs.js │ │ │ ├── usage.mdx │ │ │ ├── client.js │ │ │ ├── page.js │ │ │ ├── demo.js │ │ │ └── extension.js │ │ ├── title-bar │ │ │ ├── demo.js │ │ │ ├── page.js │ │ │ └── extension.js │ │ ├── file-icons │ │ │ ├── page.js │ │ │ ├── demo.js │ │ │ └── extension.js │ │ ├── recipe.js │ │ └── page.js │ ├── favicon.ico │ ├── opengraph-image.png │ ├── test-mdx │ │ ├── page.js │ │ └── mdx-demo.mdx │ ├── demos │ │ ├── code.tsx │ │ ├── markdown.tsx │ │ ├── line-numbers.tsx │ │ ├── title.tsx │ │ ├── custom-theme.tsx │ │ ├── global-props.tsx │ │ ├── titles-in-markdown.tsx │ │ ├── theme.tsx │ │ ├── dark-mode.tsx │ │ ├── customization.tsx │ │ ├── new-demo.tsx │ │ └── theme.json │ ├── with-background.js │ ├── test │ │ └── page.tsx │ ├── layout.js │ ├── global.css │ ├── icons.js │ └── page.tsx ├── public │ ├── favicon.ico │ ├── favicon-16x16.png │ └── favicon-32x32.png ├── .vscode │ └── settings.json ├── next-env.d.ts ├── next.config.js ├── tsconfig.json ├── package.json ├── CHANGELOG.md ├── mdx-components.tsx └── yarn.lock ├── lib ├── src │ ├── components.tsx │ ├── tokens.tsx │ ├── lines.tsx │ ├── title.tsx │ ├── types.ts │ ├── code.tsx │ └── index.tsx ├── readme.md ├── tsconfig.json ├── package.json ├── CHANGELOG.md └── yarn.lock ├── .changeset ├── config.json └── README.md ├── .gitpod.yml ├── package.json ├── .github └── workflows │ └── release.yml └── .gitignore /readme.md: -------------------------------------------------------------------------------- 1 | lib/readme.md -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /web/app/recipes/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 60, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /web/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-hike/bright/HEAD/web/app/favicon.ico -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-hike/bright/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /web/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-hike/bright/HEAD/web/app/opengraph-image.png -------------------------------------------------------------------------------- /web/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-hike/bright/HEAD/web/public/favicon-16x16.png -------------------------------------------------------------------------------- /web/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-hike/bright/HEAD/web/public/favicon-32x32.png -------------------------------------------------------------------------------- /web/app/recipes/diff/demo.js: -------------------------------------------------------------------------------- 1 | import Usage from "./usage.mdx" 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /web/app/test-mdx/page.js: -------------------------------------------------------------------------------- 1 | import Content from "./mdx-demo.mdx" 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /web/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "../node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /lib/src/components.tsx: -------------------------------------------------------------------------------- 1 | import { Root, Pre } from "./code" 2 | import { Tab, TabContent, TitleBarContent } from "./title" 3 | 4 | const components = { 5 | Pre, 6 | Root, 7 | TitleBarContent, 8 | Tab, 9 | TabContent, 10 | } 11 | 12 | export default components 13 | -------------------------------------------------------------------------------- /web/app/recipes/link/extension.js: -------------------------------------------------------------------------------- 1 | /** @type {import("bright").Extension} */ 2 | export const link = { 3 | name: "link", 4 | InlineAnnotation: ({ children, query }) => ( 5 | 6 | {children} 7 | 8 | ), 9 | } 10 | -------------------------------------------------------------------------------- /web/app/recipes/diff/diff.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Code } from "bright" 3 | import { diff } from "./extension" 4 | 5 | export function Diff({ children }) { 6 | return ( 7 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "minor", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /web/next.config.js: -------------------------------------------------------------------------------- 1 | const withMDX = require("@next/mdx")({ 2 | experimental: { 3 | mdxRs: true, 4 | }, 5 | extension: /\.mdx?$/, 6 | }) 7 | 8 | /** @type {import('next').NextConfig} */ 9 | const nextConfig = withMDX({ 10 | pageExtensions: ["ts", "tsx", "js", "jsx", "md", "mdx"], 11 | experimental: { appDir: true, mdxRs: true }, 12 | }) 13 | 14 | module.exports = nextConfig 15 | -------------------------------------------------------------------------------- /web/app/recipes/collapse/extension.js: -------------------------------------------------------------------------------- 1 | import { CollapseAnnotation } from "./collapse" 2 | 3 | /** @type {import("bright").Extension} */ 4 | export const collapse = { 5 | name: "collapse", 6 | MultilineAnnotation: ({ children, query, brightProps }) => ( 7 | 12 | ), 13 | } 14 | -------------------------------------------------------------------------------- /web/app/recipes/diff/usage.mdx: -------------------------------------------------------------------------------- 1 | import { Diff } from "./diff" 2 | 3 | 4 | 5 | ```js 6 | function lorem(ipsum, dolor = 1) { 7 | const sit = ipsum == null ? 0 : ipsum.sit 8 | dolor = sit - amet(dolor) 9 | return dolor 10 | } 11 | ``` 12 | 13 | ```js 14 | function lorem(ipsum, dolor = 1) { 15 | const sit = 0 16 | dolor = sit - amet(dolor) 17 | dolor *= 2 18 | return dolor 19 | } 20 | ``` 21 | 22 | 23 | -------------------------------------------------------------------------------- /web/app/recipes/focus/demo.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { focus } from "./extension" 3 | 4 | const myCode = ` 5 | function lorem(ipsum, dolor = 1) { 6 | const sit = ipsum == null ? 0 : ipsum.sit; 7 | // focus(1:2) 8 | dolor = sit - amet(dolor); 9 | return dolor; 10 | } 11 | `.trim() 12 | 13 | export default function Page() { 14 | return ( 15 | 16 | {myCode} 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart 6 | 7 | tasks: 8 | - init: yarn install && yarn run build 9 | command: yarn run dev 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/app/recipes/link/demo.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { link } from "./extension" 3 | 4 | const myCode = ` 5 | // link[10:14] https://github.com/sponsors/code-hike 6 | function lorem(ipsum, dolor = 1) { 7 | const sit = ipsum == null ? 0 : ipsum.sit; 8 | dolor = sit - amet(dolor); 9 | return dolor; 10 | }`.trim() 11 | 12 | export default function Page() { 13 | return ( 14 | 15 | {myCode} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/tabs.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { tabs } from "./extension" 3 | 4 | /** @type {import("bright").Extension} */ 5 | const title = { 6 | name: "title", 7 | beforeHighlight: (props, annotations) => { 8 | if (annotations.length > 0) { 9 | return { ...props, title: annotations[0].query } 10 | } 11 | }, 12 | } 13 | 14 | export function Tabs({ children }) { 15 | return ( 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /web/app/recipes/title-bar/demo.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { titleBar } from "./extension" 3 | 4 | const myCode = ` 5 | function lorem(ipsum, dolor = 1) { 6 | const sit = ipsum == null ? 0 : ipsum.sit; 7 | dolor = sit - amet(dolor); 8 | return dolor; 9 | } 10 | `.trim() 11 | 12 | export default function Page() { 13 | return ( 14 | 19 | {myCode} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "lib", 6 | "web" 7 | ] 8 | }, 9 | "scripts": { 10 | "dev": "yarn workspace bright watch & yarn workspace bright-web dev", 11 | "changeset": "changeset", 12 | "version": "changeset version", 13 | "build": "yarn workspace bright build", 14 | "release": "yarn build && changeset publish" 15 | }, 16 | "repository": "code-hike/bright", 17 | "author": "pomber", 18 | "dependencies": { 19 | "@changesets/cli": "^2.26.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/readme.md: -------------------------------------------------------------------------------- 1 | > the future is bright 2 | 3 | ## Usage 4 | 5 | ```bash 6 | npm install bright 7 | ``` 8 | 9 | Use it from a **server component**, for example in Next.js `app/page.js`: 10 | 11 | ```js 12 | import { Code } from "bright" 13 | 14 | export default function Page() { 15 | return print("hello brightness") 16 | } 17 | ``` 18 | 19 | Docs: https://bright.codehike.org 20 | 21 | ## Credits 22 | 23 | - Thanks [LEI Zongmin](https://github.com/leizongmin) for providing the bright npm package name 24 | 25 | ## License 26 | 27 | MIT 28 | -------------------------------------------------------------------------------- /web/app/demos/code.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | export default function Page() { 7 | return ( 8 | print("hello brightness") 9 | ) 10 | } 11 | `.trim() 12 | 13 | export default function Demo() { 14 | return ( 15 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /web/app/recipes/focus/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./demo.js" 4 | import rawExtension from "!!raw-loader!./extension.js" 5 | 6 | import { Recipe } from "../recipe" 7 | 8 | const data = { 9 | title: "Focus", 10 | id: "focus", 11 | Demo, 12 | source: { 13 | subProps: [ 14 | { title: "app/page.js", code: rawDemo, lang: "jsx" }, 15 | { 16 | title: "app/extension.js", 17 | code: rawExtension, 18 | lang: "jsx", 19 | }, 20 | ], 21 | }, 22 | } 23 | 24 | export default function Page() { 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /web/app/recipes/link/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./demo.js" 4 | import rawExtension from "!!raw-loader!./extension.js" 5 | 6 | import { Recipe } from "../recipe" 7 | 8 | const data = { 9 | title: "Link Annotation", 10 | id: "link", 11 | Demo, 12 | source: { 13 | subProps: [ 14 | { title: "app/page.js", code: rawDemo, lang: "jsx" }, 15 | { 16 | title: "app/extension.js", 17 | code: rawExtension, 18 | lang: "jsx", 19 | }, 20 | ], 21 | }, 22 | } 23 | 24 | export default function Page() { 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /web/app/recipes/title-bar/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./demo.js" 4 | import rawExtension from "!!raw-loader!./extension.js" 5 | 6 | import { Recipe } from "../recipe" 7 | 8 | const data = { 9 | title: "Title Bar", 10 | id: "title", 11 | Demo, 12 | source: { 13 | subProps: [ 14 | { title: "app/page.js", code: rawDemo, lang: "jsx" }, 15 | { 16 | title: "app/extension.js", 17 | code: rawExtension, 18 | lang: "jsx", 19 | }, 20 | ], 21 | }, 22 | } 23 | 24 | export default function Page() { 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /web/app/recipes/collapse/demo.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { collapse } from "./extension" 3 | 4 | const myCode = ` 5 | // collapse(1:5) close 6 | function lorem(ipsum, dolor = 1) { 7 | const sit = ipsum == null ? 0 : ipsum.sit; 8 | dolor = sit - amet(dolor); 9 | return dolor; 10 | } 11 | 12 | // collapse(1:4) 13 | function consectetur(...adipiscing) { 14 | const elit = adipiscing[0]; 15 | return sed.eiusmod(elit) ? elit : [elit]; 16 | } 17 | `.trim() 18 | 19 | export default function Page() { 20 | return ( 21 | 22 | {myCode} 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /web/app/demos/markdown.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | // You need this file to use MDX in server components 7 | // link[20:35] https://beta.nextjs.org/docs/guides/mdx 8 | // Learn more from the Next.js docs 9 | 10 | export function useMDXComponents(components) { 11 | return { ...components, pre: Code } 12 | } 13 | `.trim() 14 | 15 | export default function Demo() { 16 | return ( 17 | <> 18 |
19 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /web/app/with-background.js: -------------------------------------------------------------------------------- 1 | export function WithBackground({ 2 | children, 3 | bg = {}, 4 | fg, 5 | style = {}, 6 | blur = 50, 7 | opacity = 0.66, 8 | }) { 9 | return ( 10 |
11 |
24 | {children} 25 |
26 |
{children}
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ] 21 | }, 22 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /web/app/recipes/file-icons/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./demo.js" 4 | import rawExtension from "!!raw-loader!./extension.js" 5 | 6 | import { Recipe } from "../recipe" 7 | 8 | const data = { 9 | title: "File Icons", 10 | id: "file-icons", 11 | Demo, 12 | source: { 13 | subProps: [ 14 | { 15 | title: "app/page.js", 16 | code: rawDemo, 17 | lang: "jsx", 18 | }, 19 | { 20 | title: "app/extension.js", 21 | code: rawExtension, 22 | lang: "jsx", 23 | }, 24 | ], 25 | }, 26 | } 27 | 28 | export default function Page() { 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/usage.mdx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "./tabs" 2 | 3 | 4 | 5 | ```js 6 | // title foo.js 7 | function lorem(ipsum, dolor = 1) { 8 | const sit = ipsum == null ? 0 : ipsum.sit 9 | dolor = sit - amet(dolor) 10 | return dolor 11 | } 12 | 13 | function consectetur(...adipiscing) { 14 | const elit = adipiscing[0] 15 | return sed.eiusmod(elit) ? elit : [elit] 16 | } 17 | ``` 18 | 19 | ```py 20 | # title bar.py 21 | def dolor_sit_amet(consectetur, adipiscing): 22 | if consectetur == "Lorem" 23 | print("Pellentesque habitant.") 24 | else: 25 | print("Suspendisse potenti.") 26 | 27 | dolor_sit_amet("Lorem", "ipsum", "dolor") 28 | ``` 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "composite": false, 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "inlineSources": false, 10 | "isolatedModules": true, 11 | "moduleResolution": "node", 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "preserveWatchOutput": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "jsx": "react-jsx", 18 | "lib": ["dom", "ES2019"], 19 | "module": "ESNext", 20 | "target": "ES2019" 21 | }, 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /web/app/recipes/diff/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawUsage from "!!raw-loader!./usage.mdx" 4 | import rawDiff from "!!raw-loader!./diff.js" 5 | import rawExtension from "!!raw-loader!./extension.js" 6 | 7 | import { Recipe } from "../recipe" 8 | 9 | const data = { 10 | title: "Diff", 11 | id: "diff", 12 | Demo, 13 | source: { 14 | subProps: [ 15 | { title: "app/page.mdx", code: rawUsage, lang: "md" }, 16 | { title: "app/diff.js", code: rawDiff, lang: "jsx" }, 17 | { 18 | title: "app/extension.js", 19 | code: rawExtension, 20 | lang: "jsx", 21 | }, 22 | ], 23 | }, 24 | } 25 | 26 | export default function Page() { 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bright-web", 3 | "private": true, 4 | "version": "0.3.22", 5 | "dependencies": { 6 | "@mdx-js/react": "2.3.0", 7 | "@next/font": "13.4.10", 8 | "@next/mdx": "13.4.10", 9 | "@radix-ui/react-icons": "^1.1.1", 10 | "@radix-ui/react-tabs": "^1.0.2", 11 | "bright": "1.0.0", 12 | "diff": "^5.1.0", 13 | "next": "13.4.10", 14 | "raw-loader": "^4.0.2", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "seti-icons": "^0.0.4" 18 | }, 19 | "scripts": { 20 | "dev": "next", 21 | "build": "next build", 22 | "start": "next start" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^18.16.2", 26 | "typescript": "^5.1.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/app/recipes/collapse/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./demo.js" 4 | import rawCollapse from "!!raw-loader!./collapse.js" 5 | import rawExtension from "!!raw-loader!./extension.js" 6 | 7 | import { Recipe } from "../recipe" 8 | 9 | const data = { 10 | title: "Collapse", 11 | id: "collapse", 12 | Demo, 13 | source: { 14 | subProps: [ 15 | { title: "app/page.js", code: rawDemo, lang: "jsx" }, 16 | { 17 | title: "app/extension.js", 18 | code: rawExtension, 19 | lang: "jsx", 20 | }, 21 | { title: "app/collapse.js", code: rawCollapse, lang: "jsx" }, 22 | ], 23 | }, 24 | } 25 | 26 | export default function Page() { 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/client.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as Tabs from "@radix-ui/react-tabs" 4 | import React from "react" 5 | 6 | export function TabsRoot({ children, defaultValue }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | 14 | export function TabsList({ titles, children }) { 15 | const tabs = React.Children.toArray(children) 16 | return ( 17 | 18 | {titles.map((title, i) => ( 19 | 20 | {tabs[i]} 21 | 22 | ))} 23 | 24 | ) 25 | } 26 | 27 | export function TabsContent(props) { 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /web/app/test/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Code } from "bright" 3 | 4 | Code.theme = { 5 | dark: "min-dark", 6 | light: "min-light", 7 | lightSelector: ".light", 8 | } 9 | Code.extensions = [] 10 | const code = ` 11 |
17 | Hello World 18 |
`.trim() 19 | 20 | export default function Page() { 21 | return ( 22 |
23 |
24 | {code} 25 |
26 |
27 | {code} 28 |
29 |
30 | {code} 31 |
32 |
33 | ) 34 | } 35 | 36 | // export const runtime = "edge" 37 | -------------------------------------------------------------------------------- /web/app/recipes/recipe.js: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | import { Code } from "bright" 3 | import { tabs } from "./tabs/extension" 4 | 5 | export function Recipe({ title, id, Demo, source }) { 6 | return ( 7 |
8 |

9 | Bright /{" "} 10 | Recipes / {title} 11 |

12 |

Demo

13 | 14 |

Source

15 | 21 | 25 | Link to the demo on GitHub 26 | 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /web/app/demos/line-numbers.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | const myCode = \` 7 | let hello = "hello brightness" 8 | console.log(hello, "my old friend") 9 | \`.trim() 10 | 11 | // focus(3) 12 | export default function Page() { 13 | return ( 14 | {myCode} 15 | ) 16 | } 17 | `.trim() 18 | 19 | export default function Demo() { 20 | return ( 21 | <> 22 |
23 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /web/app/demos/title.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | const myCode = \` 7 | let hello = "hello brightness" 8 | console.log(hello, "my old friend") 9 | \`.trim() 10 | 11 | // focus(3) 12 | export default function Page() { 13 | return ( 14 | {myCode} 15 | ) 16 | } 17 | `.trim() 18 | 19 | export default function Demo() { 20 | return ( 21 | <> 22 |
23 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /web/app/demos/custom-theme.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | // focus(1:6) 7 | // you can make your own theme 8 | // or extend any VS Code theme 9 | // link[9:42] https://themes.codehike.org/editor 10 | // with https://themes.codehike.org/editor 11 | import myTheme from "./my-theme.json" 12 | 13 | Code.theme = myTheme 14 | 15 | const myCode = \` 16 | theFuture, bright = 10, 10 17 | print(theFuture is bright) 18 | \`.trim() 19 | 20 | export default function Page() { 21 | return ( 22 | {myCode} 23 | ) 24 | } 25 | `.trim() 26 | 27 | export default function Demo() { 28 | return ( 29 | <> 30 |
31 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /web/app/recipes/file-icons/demo.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { fileIcons } from "./extension" 3 | 4 | const myCode = ` 5 | function lorem(ipsum, dolor = 1) { 6 | const sit = ipsum == null ? 0 : ipsum.sit; 7 | dolor = sit - amet(dolor); 8 | return dolor; 9 | } 10 | `.trim() 11 | 12 | export default function Page() { 13 | return ( 14 | <> 15 | 20 | {myCode} 21 | 22 | 28 | {myCode} 29 | 30 | 35 | {myCode} 36 | 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/page.js: -------------------------------------------------------------------------------- 1 | import Demo from "./demo" 2 | 3 | import rawDemo from "!!raw-loader!./usage.mdx" 4 | import rawTabs from "!!raw-loader!./tabs.js" 5 | import rawExtension from "!!raw-loader!./extension.js" 6 | import rawClient from "!!raw-loader!./client.js" 7 | 8 | import { Recipe } from "../recipe" 9 | 10 | const data = { 11 | title: "Tabs", 12 | id: "tabs", 13 | Demo, 14 | source: { 15 | subProps: [ 16 | { title: "app/page.mdx", code: rawDemo, lang: "md" }, 17 | { title: "app/tabs.js", code: rawTabs, lang: "jsx" }, 18 | { 19 | title: "app/extension.js", 20 | code: rawExtension, 21 | lang: "jsx", 22 | }, 23 | { 24 | title: "app/client.js", 25 | code: rawClient, 26 | lang: "jsx", 27 | }, 28 | ], 29 | }, 30 | } 31 | 32 | export default function Page() { 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bright", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "module": "./dist/index.js", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "repository": "https://github.com/code-hike/bright", 9 | "homepage": "https://bright.codehike.org", 10 | "funding": "https://github.com/code-hike/bright?sponsor=1", 11 | "license": "MIT", 12 | "files": [ 13 | "dist/**" 14 | ], 15 | "scripts": { 16 | "build": "tsup src/index.tsx --format esm --dts", 17 | "watch": "tsup src/index.tsx --format esm --watch --dts" 18 | }, 19 | "dependencies": { 20 | "@code-hike/lighter": "^1.0.2", 21 | "server-only": "^0.0.1" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.0.26", 25 | "react": "18.2.0", 26 | "tsup": "6.5.0", 27 | "typescript": "4.9.4" 28 | }, 29 | "peerDependencies": { 30 | "react": "^18 || ^19" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/app/demos/global-props.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | const myCode = \` 7 | let hello = "hello brightness" 8 | console.log(hello, "my old friend") 9 | \`.trim() 10 | 11 | // focus(1:2) 12 | // set any prop globally 13 | Code.lineNumbers = true 14 | 15 | export default function Page() { 16 | return ( 17 | {myCode} 18 | ) 19 | } 20 | `.trim() 21 | 22 | export default function Demo() { 23 | return ( 24 | <> 25 |
26 | 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /web/app/demos/titles-in-markdown.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = `# Hello 4 | 5 | This is how you add the code's title in Markdown/MDX 6 | 7 | \`\`\`web/shine.js 8 | let hello = "hello brightness" 9 | console.log(hello, "my old friend") 10 | \`\`\` 11 | ` 12 | 13 | export default function Demo() { 14 | return ( 15 | <> 16 |
17 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /web/app/layout.js: -------------------------------------------------------------------------------- 1 | import "./global.css" 2 | import { Inter } from "@next/font/google" 3 | 4 | const inter = Inter({ 5 | weight: ["400", "700", "800"], 6 | subsets: ["latin"], 7 | }) 8 | 9 | export const metadata = { 10 | title: "Bright - Syntax Highlighting React Server Component", 11 | description: "A server component for syntax highlighting.", 12 | keywords: "React Server Component, Syntax Highlighting, Code Hike", 13 | authors: [{ name: "Code Hike", url: "https://codehike.org/" }], 14 | colorScheme: "dark", 15 | twitter: { 16 | card: "summary_large_image", 17 | title: "Bright - Syntax Highlighting React Server Component", 18 | description: "A server component for syntax highlighting.", 19 | creator: "@codehike_", 20 | }, 21 | } 22 | 23 | export default function RootLayout({ children }) { 24 | return ( 25 | 26 | 27 | {children} 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /web/app/demos/theme.tsx: -------------------------------------------------------------------------------- 1 | import { NewDemo } from "./new-demo" 2 | 3 | const sourceCode = ` 4 | import { Code } from "bright" 5 | 6 | const myCode = \` 7 | theFuture, bright = 10, 10 8 | print(theFuture is bright) 9 | \`.trim() 10 | 11 | // focus(1:3) 12 | // there are several themes built in 13 | // typescript should autocomplete the options 14 | Code.theme = "github-light" 15 | 16 | export default function Page() { 17 | return ( 18 | {myCode} 19 | ) 20 | } 21 | `.trim() 22 | 23 | export default function Demo() { 24 | return ( 25 | <> 26 |
27 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /web/app/recipes/collapse/collapse.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useState } from "react" 4 | import { 5 | ChevronRightIcon, 6 | ChevronDownIcon, 7 | } from "@radix-ui/react-icons" 8 | 9 | export function CollapseAnnotation({ children, query, color }) { 10 | const firstLine = React.Children.toArray(children)[0] 11 | const [isOpen, setIsOpen] = useState(query !== "close") 12 | const Icon = isOpen ? ChevronDownIcon : ChevronRightIcon 13 | return ( 14 |
15 | 34 | {isOpen ? children :
{firstLine}
} 35 |
36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /web/app/recipes/title-bar/extension.js: -------------------------------------------------------------------------------- 1 | /** @type {import("bright").Extension} */ 2 | export const titleBar = { 3 | name: "titleBar", 4 | TitleBarContent: Title, 5 | } 6 | 7 | /** @type {import("bright").BrightProps["TitleBarContent"]} */ 8 | function Title(props) { 9 | const { title, colors } = props 10 | const { foreground, background } = colors 11 | 12 | const circle = { 13 | borderRadius: "100%", 14 | height: "0.8em", 15 | width: "0.8em", 16 | background: foreground, 17 | opacity: 0.2, 18 | } 19 | 20 | return ( 21 |
33 |
36 |
37 |
38 |
39 |
40 | {title} 41 |
42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/demo.js: -------------------------------------------------------------------------------- 1 | import Usage from "./usage.mdx" 2 | 3 | export default function Page() { 4 | return 5 | } 6 | 7 | // export default function Page() { 8 | // const props = { 9 | // extensions: [tabs], 10 | // subProps: [ 11 | // { code: "console.log(1)", lang: "js", title: "foo.js" }, 12 | // { code: "print(2)", lang: "py", title: "bar.py" }, 13 | // { code: "print(3)", lang: "py", title: "readme.graphql" }, 14 | // ], 15 | // } 16 | // return ( 17 | // <> 18 | // 19 | // 20 | // 21 | // Github Light 22 | // 23 | // 24 | // 25 | // 26 | // 27 | // 34 | // 35 | // ) 36 | // } 37 | -------------------------------------------------------------------------------- /web/app/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-feature-settings: "rlig" 1, "calt" 0; 3 | text-rendering: optimizeLegibility; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | color-scheme: dark; 7 | font-size: clamp(0.5rem, 0.04rem + 2.2857vw, 0.95rem); 8 | } 9 | 10 | h2 { 11 | font-weight: 800; 12 | } 13 | 14 | body { 15 | background-color: #010409; 16 | color: #eaeaea; 17 | max-width: 640px; 18 | margin: 0 auto; 19 | padding: 0 2rem; 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | a { 27 | color: #aaa; 28 | text-decoration: none; 29 | } 30 | 31 | a:hover { 32 | color: var(--hover-color, #eaeaea) !important; 33 | } 34 | 35 | ul { 36 | list-style: none; 37 | padding: 0; 38 | margin: 0; 39 | font-weight: 500; 40 | } 41 | 42 | li { 43 | list-style: none; 44 | line-height: 3.2rem; 45 | text-align: center; 46 | display: flex; 47 | justify-content: space-between; 48 | gap: 0.5ch; 49 | } 50 | 51 | /* if mobile */ 52 | @media (max-width: 600px) { 53 | body { 54 | --description-font-size: 2.6rem; 55 | } 56 | } 57 | 58 | .demo-pre ::selection { 59 | background-color: #61616150; 60 | } 61 | 62 | .demo-pre code.focused > span { 63 | /* filter: brightness(0.5); */ 64 | filter: contrast(0.3); 65 | transition: filter 0.5s; 66 | } 67 | /* .demo-pre:hover code.focused > span { 68 | filter: unset; 69 | } */ 70 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js 16.x 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 16.x 22 | 23 | - name: Install Dependencies 24 | run: yarn 25 | 26 | # This step will run changeset version, setting the step output to if there were changesets found 27 | - name: Version command 28 | id: version 29 | run: | 30 | echo ::set-output name=changes::$(npx changeset version 2>&1 | grep -q 'No unreleased changesets found' && echo 'false' || echo 'true') 31 | 32 | - name: Release packages 33 | if: steps.version.outputs.changes == 'true' 34 | uses: changesets/action@v1 35 | with: 36 | publish: yarn release 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 40 | 41 | - name: Push changes 42 | if: steps.version.outputs.changes == 'true' 43 | run: | 44 | git config user.name "GitHub Action" 45 | git add -A 46 | git commit -m "New version" || echo "No changes to commit" 47 | git push 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /web/app/test-mdx/mdx-demo.mdx: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | ``` 4 | hello 5 | ``` 6 | 7 | ```foo. 8 | hello 9 | ``` 10 | 11 | ```whatever 12 | hello 13 | ``` 14 | 15 | # Hello 16 | 17 | ```web/shine.js 18 | let hello = "hello brightness" 19 | console.log(hello, "my old friend") 20 | ``` 21 | 22 | ```app/page.jsx 23 | import { Code } from "bright" 24 | 25 | Code.extensions = { 26 | mark: { 27 | InlineAnnotation: ({ children, query }) => ( 28 | {children} 29 | ), 30 | MultilineAnnotation: ({ children, query }) => ( 31 | {children} 32 | ), 33 | }, 34 | number: ({ children, content }) => ( 35 | 36 | ), 37 | title: { 38 | beforeHighlight: (props, query) => ({ 39 | ...props, 40 | title: query, 41 | }), 42 | }, 43 | } 44 | 45 | const myCode = ` 46 | // mark[3:10] red 47 | console.log(1) 48 | // mark 49 | const x = 20 50 | ` 51 | 52 | export default function Page() { 53 | return {myCode} 54 | } 55 | ``` 56 | 57 | ````md 58 | ```js 59 | /// mark[3:10] red 60 | console.log(1) 61 | /// number[11:12] 62 | const x = 20 63 | ``` 64 | ```` 65 | 66 | ## Result 67 | 68 | ```js 69 | // title foo.js 70 | console.log(1) // mark[3:10] red 71 | // mark 72 | const x = 20 73 | ``` 74 | 75 | ## Mark code with space 76 | 77 | ````md 78 | ``` 79 | 80 | 81 | here is some text with leading and trailing spaces 82 | that is working if the two lines are left-aligned. 83 | 84 | 85 | ``` 86 | ```` 87 | 88 | ``` 89 | 90 | 91 | here is some text with leading and trailing spaces 92 | that is working if the two lines are left-aligned. 93 | 94 | 95 | ``` 96 | -------------------------------------------------------------------------------- /web/app/recipes/file-icons/extension.js: -------------------------------------------------------------------------------- 1 | import { themeIcons } from "seti-icons" 2 | 3 | /** @type {import("bright").Extension} */ 4 | export const fileIcons = { 5 | name: "fileIcons", 6 | TabContent: MyTab, 7 | } 8 | 9 | /** @type {import("bright").BrightProps["TabContent"]} */ 10 | function MyTab(props) { 11 | const { title, colors } = props 12 | 13 | const { svg, color } = 14 | colors.colorScheme === "dark" 15 | ? getDarkIcon(title) 16 | : getLightIcon(title) 17 | const __html = svg.replace(/svg/, `svg fill='${color}'`) 18 | return ( 19 |
27 | 36 | {title} 37 |
38 | ) 39 | } 40 | 41 | // colors from https://github.com/microsoft/vscode/blob/main/extensions/theme-seti/icons/vs-seti-icon-theme.json 42 | const getDarkIcon = themeIcons({ 43 | blue: "#519aba", 44 | grey: "#4d5a5e", 45 | "grey-light": "#6d8086", 46 | green: "#8dc149", 47 | orange: "#e37933", 48 | pink: "#f55385", 49 | purple: "#a074c4", 50 | red: "#cc3e44", 51 | white: "#d4d7d6", 52 | yellow: "#cbcb41", 53 | ignore: "#41535b", 54 | }) 55 | 56 | const getLightIcon = themeIcons({ 57 | blue: "#498ba7", 58 | grey: "#455155", 59 | "grey-light": "#627379", 60 | green: "#7fae42", 61 | orange: "#cc6d2e", 62 | pink: "#dd4b78", 63 | purple: "#9068b0", 64 | red: "#b8383d", 65 | white: "#bfc2c1", 66 | yellow: "#b7b73b", 67 | ignore: "#3b4b52", 68 | }) 69 | -------------------------------------------------------------------------------- /web/app/recipes/page.js: -------------------------------------------------------------------------------- 1 | import CollapseAnnotationDemo from "./collapse/demo" 2 | import LinkAnnotationDemo from "./link/demo" 3 | import TitleBarDemo from "./title-bar/demo" 4 | import FileIconsDemo from "./file-icons/demo" 5 | import DiffDemo from "./diff/demo" 6 | import TabsDemo from "./tabs/demo" 7 | import FocusDemo from "./focus/demo" 8 | import Link from "next/link" 9 | 10 | const recipes = [ 11 | { 12 | title: "Collapse", 13 | id: "collapse", 14 | Demo: CollapseAnnotationDemo, 15 | }, 16 | { 17 | title: "Diff", 18 | id: "diff", 19 | Demo: DiffDemo, 20 | }, 21 | { 22 | title: "File Icons", 23 | id: "file-icons", 24 | Demo: FileIconsDemo, 25 | }, 26 | { 27 | title: "Focus", 28 | id: "focus", 29 | Demo: FocusDemo, 30 | }, 31 | { 32 | title: "Link", 33 | id: "link", 34 | Demo: LinkAnnotationDemo, 35 | }, 36 | { 37 | title: "Tabs", 38 | id: "tabs", 39 | Demo: TabsDemo, 40 | }, 41 | { 42 | title: "Title Bar", 43 | id: "title-bar", 44 | Demo: TitleBarDemo, 45 | }, 46 | ] 47 | 48 | export default function Page() { 49 | return ( 50 |
51 |

52 | Bright / Recipes 53 |

54 | {recipes.map(({ title, id, Demo }) => ( 55 |
56 |
63 |

{title}

64 | 68 | view source 69 | 70 |
71 | 72 |
73 | ))} 74 |
75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /web/app/recipes/tabs/extension.js: -------------------------------------------------------------------------------- 1 | import { Code } from "bright" 2 | import { TabsRoot, TabsContent, TabsList } from "./client" 3 | 4 | /** @type {import("bright").BrightProps["TitleBarContent"]} */ 5 | function TitleBarComponent(brightProps) { 6 | const { subProps, title, Tab } = brightProps 7 | const titles = subProps?.length 8 | ? subProps.map((subProp) => subProp.title) 9 | : [title] 10 | const childProps = subProps?.length 11 | ? subProps 12 | : [brightProps] 13 | return ( 14 | 15 | {titles.map((title, i) => ( 16 | 17 | ))} 18 | 19 | ) 20 | } 21 | 22 | /** @type {import("bright").BrightProps["Root"]} */ 23 | function Root(brightProps) { 24 | const { subProps, title } = brightProps 25 | 26 | const titles = subProps?.length 27 | ? subProps.map((subProp) => subProp.title) 28 | : [title] 29 | 30 | return ( 31 | 32 |