love-ui 1.2.8 → 1.2.10

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.
Files changed (76) hide show
  1. package/dist/mcp-server.js +1 -1
  2. package/package.json +1 -1
  3. package/packages/code-block/README.md +62 -0
  4. package/packages/code-block/components.json +21 -0
  5. package/packages/code-block/package.json +46 -22
  6. package/packages/code-block/src/components/code-block/blocks/copy-with-select-package-manager.tsx +146 -0
  7. package/packages/code-block/src/components/code-block/blocks/copy-with-tabs-package-manager.tsx +110 -0
  8. package/packages/code-block/src/components/code-block/blocks/inline-code.tsx +28 -0
  9. package/packages/code-block/src/components/code-block/blocks/multi-tabs.tsx +118 -0
  10. package/packages/code-block/src/components/code-block/client/shiki.tsx +139 -0
  11. package/packages/code-block/src/components/code-block/client/sugar-high.tsx +38 -0
  12. package/packages/code-block/src/components/code-block/code-block.tsx +113 -0
  13. package/packages/code-block/src/components/code-block/copy-button.tsx +59 -0
  14. package/packages/code-block/src/components/code-block/mdx/pre-shiki.tsx +42 -0
  15. package/packages/code-block/src/components/code-block/mdx/pre-sugar-high.tsx +37 -0
  16. package/packages/code-block/src/components/ui/dropdown-menu.tsx +103 -0
  17. package/packages/code-block/src/components/ui/tabs.tsx +84 -0
  18. package/packages/code-block/src/index.ts +35 -0
  19. package/packages/code-block/src/stores/packageManager.ts +24 -0
  20. package/packages/code-block/src/styles/globals.css +79 -0
  21. package/packages/code-block/src/styles/shiki.css +95 -0
  22. package/packages/code-block/src/styles/sugar-high.css +48 -0
  23. package/packages/code-block/src/utils/cn.ts +6 -0
  24. package/packages/code-block/src/utils/copy.ts +9 -0
  25. package/packages/code-block/src/utils/react-to-text.ts +34 -0
  26. package/packages/code-block/src/utils/shiki/highlight.ts +47 -0
  27. package/packages/code-block/src/utils/sugar-high/highlight.ts +12 -0
  28. package/packages/code-block/tsconfig.json +13 -9
  29. package/packages/gooey-toast/package.json +28 -0
  30. package/packages/gooey-toast/src/gooey.tsx +614 -0
  31. package/packages/gooey-toast/src/icons.tsx +68 -0
  32. package/packages/gooey-toast/src/index.ts +10 -0
  33. package/packages/gooey-toast/src/styles.css +511 -0
  34. package/packages/gooey-toast/src/toast.tsx +444 -0
  35. package/packages/gooey-toast/src/types.ts +45 -0
  36. package/packages/gooey-toast/tsconfig.json +13 -0
  37. package/packages/gradient-mesh/index.tsx +236 -0
  38. package/packages/gradient-mesh/package.json +62 -0
  39. package/packages/gradient-mesh/tsconfig.json +10 -0
  40. package/packages/love-ui/src/components/site-header.tsx +3 -1
  41. package/packages/love-ui/src/hooks/use-media.tsx +22 -0
  42. package/packages/love-ui/src/styles/globals.css +3 -1
  43. package/packages/love-ui/src/ui/alert-dialog.tsx +3 -2
  44. package/packages/love-ui/src/ui/alert.tsx +5 -4
  45. package/packages/love-ui/src/ui/breadcrumb.tsx +7 -6
  46. package/packages/love-ui/src/ui/button.tsx +22 -4
  47. package/packages/love-ui/src/ui/card.tsx +32 -11
  48. package/packages/love-ui/src/ui/chart.tsx +367 -0
  49. package/packages/love-ui/src/ui/checkbox.tsx +42 -35
  50. package/packages/love-ui/src/ui/command.tsx +2 -1
  51. package/packages/love-ui/src/ui/dialog.tsx +5 -4
  52. package/packages/love-ui/src/ui/empty.tsx +7 -6
  53. package/packages/love-ui/src/ui/frame.tsx +7 -6
  54. package/packages/love-ui/src/ui/kbd.tsx +27 -0
  55. package/packages/love-ui/src/ui/label.tsx +2 -1
  56. package/packages/love-ui/src/ui/menu.tsx +2 -1
  57. package/packages/love-ui/src/ui/number-field.tsx +2 -1
  58. package/packages/love-ui/src/ui/pagination.tsx +5 -4
  59. package/packages/love-ui/src/ui/popover.tsx +4 -1
  60. package/packages/love-ui/src/ui/select.tsx +11 -2
  61. package/packages/love-ui/src/ui/sheet.tsx +5 -4
  62. package/packages/love-ui/src/ui/skeleton.tsx +3 -1
  63. package/packages/love-ui/src/ui/table.tsx +9 -8
  64. package/packages/love-ui/src/ui/textarea.tsx +2 -1
  65. package/packages/patterns/combobox/rich-content/combobox-rich-content-4.tsx +1 -1
  66. package/packages/patterns/item/interactive/item-interactive-2.tsx +1 -1
  67. package/packages/patterns/scroll-area/layout/scroll-area-layout-3.tsx +1 -1
  68. package/packages/patterns/table/standard/table-standard-1.tsx +1 -1
  69. package/packages/patterns/table/standard/table-standard-2.tsx +1 -1
  70. package/packages/shadcn-ui/components/ui/card.tsx +25 -5
  71. package/packages/shader-ripple/index.tsx +303 -0
  72. package/packages/shader-ripple/package.json +60 -0
  73. package/packages/shader-ripple/tsconfig.json +18 -0
  74. package/packages/silk/types.d.ts +16 -6
  75. package/packages/code-block/index.tsx +0 -638
  76. package/packages/code-block/server.tsx +0 -63
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Server as ge}from"@modelcontextprotocol/sdk/server";var L={name:"love-ui",version:"1.3.0",private:!1,license:"AGPL-3.0-or-later",type:"module",bin:{loveui:"dist/index.js","love-ui":"dist/index.js","loveui-mcp":"dist/mcp-server.js"},files:["dist","packages","*.ts","*.tsx","*.css","*.mdx"],main:"packages/love-ui/index.ts",types:"packages/love-ui/index.ts",exports:{".":{import:"./packages/love-ui/index.ts",require:"./packages/love-ui/index.ts"},"./lib/*":"./packages/love-ui/*","./components/*":"./packages/love-ui/components/*","./shadcn-ui/*":"./packages/shadcn-ui/*","./package.json":"./package.json"},dependencies:{"@modelcontextprotocol/sdk":"^1.18.1",postcss:"^8.5.6","postcss-nested":"^7.0.2"},scripts:{build:"tsup",postbuild:"node ./scripts/copy-packages.mjs",clean:"rimraf dist packages"},devDependencies:{"@types/node":"^20.14.10",rimraf:"^6.0.1",tsup:"^8.5.0"}};import{constants as X}from"fs";import{access as H,readFile as N,readdir as C}from"fs/promises";import D,{extname as W,join as P,relative as V}from"path";import{fileURLToPath as Y}from"url";import A from"postcss";import Q from"postcss-nested";var Z=D.dirname(Y(import.meta.url)),ee=D.resolve(Z,".."),U=D.join(ee,"packages"),te=new Set([".turbo",".next",".git","dist","build","storybook-static","node_modules","__tests__","__mocks__","coverage"]),se=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".json",".css",".scss",".sass",".mdx"]),ne=new Set(["package.json","tsconfig.json","tsconfig.build.json","tsconfig.test.json","README.md",".DS_Store"]),ie=new Set(["shadcn-ui","typescript-config","patterns","loveui","love-ui"]),re=e=>e.replace(/\\/g,"/"),J=async(e,s,t)=>{let n=await C(e,{withFileTypes:!0});for(let i of n){let o=P(e,i.name);if(i.isDirectory()){if(te.has(i.name))continue;await J(o,s,t);continue}let d=W(i.name);!se.has(d)||ne.has(i.name)||i.name.endsWith(".d.ts")||i.name.endsWith(".test.ts")||i.name.endsWith(".test.tsx")||i.name.endsWith(".stories.tsx")||t.push({absolute:o,relative:re(V(s,o))})}},oe=(e,s)=>{let t=e.loveui??{},n=typeof t.target=="string"?t.target.trim():"",i=t.category;if(n.length>0){let o=typeof t.includePackageName=="boolean"?t.includePackageName:!1;return{base:n.replace(/\/+$/,""),includePackageName:o}}return i==="feature"?{base:"components",includePackageName:!0}:i==="block"?{base:"components/blocks",includePackageName:!0}:{base:"components/ui",includePackageName:!0}},ae=e=>{let t=(e.loveui??{}).type;return typeof t=="string"&&t.trim().length>0?t:"registry:ui"},ce=async e=>{try{return await H(e,X.F_OK),!0}catch{return!1}},le=e=>e.startsWith("@loveui/")||e.startsWith("@repo/"),pe=e=>e.replace(/^@repo\//,"").replace(/^@loveui\//,""),G=async()=>(await C(U,{withFileTypes:!0})).filter(s=>s.isDirectory()).map(s=>s.name).filter(s=>!ie.has(s)).sort((s,t)=>s.localeCompare(t)),E=async e=>{let s=P(U,e),t=P(s,"package.json");if(!await ce(t))throw new Error(`Missing package.json for ${e}`);let n=JSON.parse(await N(t,"utf8")),i=ae(n),o=Object.keys(n.dependencies??{}),d=Object.keys(n.peerDependencies??{}),O=Object.keys(n.devDependencies??{}),k=new Set([...o,...d,...O].filter(le).filter(r=>r!=="@loveui/shadcn-ui")),T=[...new Set([...o,...d].filter(r=>!k.has(r)))],_=[...new Set(O.filter(r=>!k.has(r)&&!["@loveui/typescript-config","@types/react","@types/react-dom","typescript"].includes(r)))],w=[];for(let r of k){let m=pe(r);w.push(`https://www.loveui.dev/r/${m}.json`)}let I=[];await J(s,s,I);let u=[],c={},p=oe(n,e);for(let r of I){let m=await N(r.absolute,"utf8"),R=W(r.absolute);if(R===".css"||R===".scss"||R===".sass"){let y=await A([Q]).process(m,{from:void 0});A.parse(y.css).walkAtRules("layer",b=>{let j=`@layer ${b.params}`;c[j]??={},b.walkRules(a=>{if(a.parent&&a.parent.type==="atrule"&&a.parent.name==="media")return;let h=a.selector,l={};a.walkDecls(v=>{l[v.prop]=v.value}),Object.keys(l).length>0&&(c[j][h]=l)}),b.walkAtRules("media",a=>{let h=`@media ${a.params}`,l=c[j];l[h]??={};let v=l[h];a.walkRules($=>{let B=$.selector,x={};$.walkDecls(F=>{x[F.prop]=F.value}),Object.keys(x).length>0&&(v[B]=x)})})});continue}let g=r.relative.startsWith("src/")?r.relative.slice(4):r.relative;if(!p.includePackageName){let y=p.base.split("/").filter(Boolean),f=y[y.length-1];f&&g.startsWith(`${f}/`)&&(g=g.slice(f.length+1))}let q=e.includes("/")?e.split("/").pop()??e:e,z=(p.includePackageName?`${p.base}/${q}`:p.base).replace(/\/+$/,"");u.push({type:i,path:g,target:`${z}/${g}`.replace(/\/+/g,"/"),content:m})}let K=!u.length&&Object.keys(c).length>0?"registry:style":i;return{$schema:"https://ui.shadcn.com/schema/registry-item.json",name:e,type:K,title:n.title??e,description:n.description,author:n.author??"Connor Love <hello@loveconnor.com>",dependencies:T.length?T:void 0,devDependencies:_.length?_:void 0,registryDependencies:w.length?Array.from(new Set(w)):void 0,files:u.length?u:void 0,css:Object.keys(c).length?c:void 0}};var S="loveui://registry/",de=e=>`${S}${e}`;async function ue(){let e=new ge({name:"loveui-mcp",version:L.version??"0.0.0"});e.addResourceProvider({name:"loveui-registry",listResources:async()=>(await G()).map(t=>({uri:de(t),name:t,description:`loveui registry definition for ${t}`,mimeType:"application/json"})),readResource:async s=>{if(!s.startsWith(S))throw new Error(`Unsupported resource URI: ${s}`);let t=s.slice(S.length),n=await E(t);return{contents:[{uri:s,mimeType:"application/json",text:JSON.stringify(n,null,2)}]}}}),e.addTool({name:"get-loveui-package",description:"Fetch a loveui registry definition by package name.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Package name, e.g. badge"}},required:["name"]},handler:async s=>{let t=s?.name;if(typeof t!="string"||t.trim()==="")throw new Error("Package name is required.");let n=await E(t);return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}}),await e.start()}ue().catch(e=>{console.error(e),process.exit(1)});
2
+ import{constants as ke}from"fs";import{access as Se,readFile as V}from"fs/promises";import c from"path";import{fileURLToPath as Pe}from"url";import{Server as be}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as je}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Ee,ErrorCode as g,ListResourceTemplatesRequestSchema as Te,ListResourcesRequestSchema as xe,ListToolsRequestSchema as Ie,McpError as d,ReadResourceRequestSchema as _e}from"@modelcontextprotocol/sdk/types.js";var N={name:"love-ui",version:"1.2.10",private:!1,license:"AGPL-3.0-or-later",type:"module",bin:{loveui:"dist/index.js","love-ui":"dist/index.js","loveui-mcp":"dist/mcp-server.js"},files:["dist","packages","*.ts","*.tsx","*.css","*.mdx"],main:"packages/love-ui/index.ts",types:"packages/love-ui/index.ts",exports:{".":{import:"./packages/love-ui/index.ts",require:"./packages/love-ui/index.ts"},"./lib/*":"./packages/love-ui/*","./components/*":"./packages/love-ui/components/*","./shadcn-ui/*":"./packages/shadcn-ui/*","./package.json":"./package.json"},dependencies:{"@modelcontextprotocol/sdk":"^1.18.1",postcss:"^8.5.6","postcss-nested":"^7.0.2"},scripts:{build:"tsup",postbuild:"node ./scripts/copy-packages.mjs",clean:"rimraf dist packages"},devDependencies:{"@types/node":"^20.14.10",rimraf:"^6.0.1",tsup:"^8.5.0"}};import{constants as re}from"fs";import{access as ie,readFile as U,readdir as W}from"fs/promises";import I,{extname as G,join as x,relative as oe}from"path";import{fileURLToPath as ae}from"url";import q from"postcss";import ce from"postcss-nested";var le=I.dirname(ae(import.meta.url)),pe=I.resolve(le,".."),J=I.join(pe,"packages"),me=new Set([".turbo",".next",".git","dist","build","storybook-static","node_modules","__tests__","__mocks__","coverage"]),ue=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".json",".css",".scss",".sass",".mdx"]),ge=new Set(["package.json","tsconfig.json","tsconfig.build.json","tsconfig.test.json","README.md",".DS_Store"]),de=new Set(["shadcn-ui","typescript-config","patterns","loveui","love-ui"]),ye=e=>e.replace(/\\/g,"/"),H=async(e,t,s)=>{let n=await W(e,{withFileTypes:!0});for(let r of n){let o=x(e,r.name);if(r.isDirectory()){if(me.has(r.name))continue;await H(o,t,s);continue}let y=G(r.name);!ue.has(y)||ge.has(r.name)||r.name.endsWith(".d.ts")||r.name.endsWith(".test.ts")||r.name.endsWith(".test.tsx")||r.name.endsWith(".stories.tsx")||s.push({absolute:o,relative:ye(oe(t,o))})}},fe=(e,t)=>{let s=e.loveui??{},n=typeof s.target=="string"?s.target.trim():"",r=s.category;if(n.length>0){let o=typeof s.includePackageName=="boolean"?s.includePackageName:!1;return{base:n.replace(/\/+$/,""),includePackageName:o}}return r==="feature"?{base:"components",includePackageName:!0}:r==="block"?{base:"components/blocks",includePackageName:!0}:{base:"components/ui",includePackageName:!0}},he=e=>{let s=(e.loveui??{}).type;return typeof s=="string"&&s.trim().length>0?s:"registry:ui"},ve=async e=>{try{return await ie(e,re.F_OK),!0}catch{return!1}},Re=e=>e.startsWith("@loveui/")||e.startsWith("@repo/"),we=e=>e.replace(/^@repo\//,"").replace(/^@loveui\//,""),K=async()=>(await W(J,{withFileTypes:!0})).filter(t=>t.isDirectory()).map(t=>t.name).filter(t=>!de.has(t)).sort((t,s)=>t.localeCompare(s)),M=async e=>{let t=x(J,e),s=x(t,"package.json");if(!await ve(s))throw new Error(`Missing package.json for ${e}`);let n=JSON.parse(await U(s,"utf8")),r=he(n),o=Object.keys(n.dependencies??{}),y=Object.keys(n.peerDependencies??{}),D=Object.keys(n.devDependencies??{}),S=new Set([...o,...y,...D].filter(Re).filter(i=>i!=="@loveui/shadcn-ui")),A=[...new Set([...o,...y].filter(i=>!S.has(i)))],C=[...new Set(D.filter(i=>!S.has(i)&&!["@loveui/typescript-config","@types/react","@types/react-dom","typescript"].includes(i)))],P=[];for(let i of S){let h=we(i);P.push(`https://www.loveui.dev/r/${h}.json`)}let $=[];await H(t,t,$);let f=[],l={},m=fe(n,e);for(let i of $){let h=await U(i.absolute,"utf8"),b=G(i.absolute);if(b===".css"||b===".scss"||b===".sass"){let v=await q([ce]).process(h,{from:void 0});q.parse(v.css).walkAtRules("layer",j=>{let E=`@layer ${j.params}`;l[E]??={},j.walkRules(a=>{if(a.parent&&a.parent.type==="atrule"&&a.parent.name==="media")return;let w=a.selector,p={};a.walkDecls(k=>{p[k.prop]=k.value}),Object.keys(p).length>0&&(l[E][w]=p)}),j.walkAtRules("media",a=>{let w=`@media ${a.params}`,p=l[E];p[w]??={};let k=p[w];a.walkRules(L=>{let se=L.selector,T={};L.walkDecls(F=>{T[F.prop]=F.value}),Object.keys(T).length>0&&(k[se]=T)})})});continue}let u=i.relative.startsWith("src/")?i.relative.slice(4):i.relative;if(!m.includePackageName){let v=m.base.split("/").filter(Boolean),R=v[v.length-1];R&&u.startsWith(`${R}/`)&&(u=u.slice(R.length+1))}let ee=e.includes("/")?e.split("/").pop()??e:e,te=(m.includePackageName?`${m.base}/${ee}`:m.base).replace(/\/+$/,"");f.push({type:r,path:u,target:`${te}/${u}`.replace(/\/+/g,"/"),content:h})}let Z=!f.length&&Object.keys(l).length>0?"registry:style":r;return{$schema:"https://ui.shadcn.com/schema/registry-item.json",name:e,type:Z,title:n.title??e,description:n.description,author:n.author??"Connor Love <hello@loveconnor.com>",dependencies:A.length?A:void 0,devDependencies:C.length?C:void 0,registryDependencies:P.length?Array.from(new Set(P)):void 0,files:f.length?f:void 0,css:Object.keys(l).length?l:void 0}};var O="loveui://registry/",Oe="loveui://registry/{package}",B="get-loveui-package",De=e=>`${O}${e}`,Ae=c.dirname(Pe(import.meta.url)),z=c.resolve(Ae,".."),Ce=[c.join(z,"public","r"),c.resolve(z,"..","..","apps","ui","public","r"),c.resolve(process.cwd(),"apps","ui","public","r")],Y=async e=>{try{return await Se(e,ke.R_OK),!0}catch{return!1}},_=null,Q=async()=>(_||(_=(async()=>{for(let e of Ce){let t=c.join(e,"registry.json");if(await Y(t))try{let s=JSON.parse(await V(t,"utf8")),n=Array.from(new Set((s.items??[]).map(r=>r.name?.trim()).filter(r=>!!r))).sort((r,o)=>r.localeCompare(o));if(n.length===0)continue;return{dir:e,names:n,nameSet:new Set(n)}}catch{continue}}return null})()),_),$e=async()=>{let[e,t]=await Promise.all([Q(),K()]),s=new Set(t);for(let n of e?.names??[])s.add(n);return Array.from(s).sort((n,r)=>n.localeCompare(r))},Le=async e=>{let t=await Q();if(!t||!t.nameSet.has(e))return null;let s=c.join(t.dir,`${e}.json`);return await Y(s)?JSON.parse(await V(s,"utf8")):null},Fe=e=>{if(!e.startsWith(O))throw new d(g.InvalidParams,`Unsupported resource URI: ${e}`);let t=decodeURIComponent(e.slice(O.length)).trim();if(!t)throw new d(g.InvalidParams,"Package name is required.");return t},X=async e=>{try{let t=await Le(e);return t||await M(e)}catch(t){throw new d(g.InvalidParams,t instanceof Error?t.message:String(t))}};async function Ne(){let e=new be({name:"loveui-mcp",version:N.version??"0.0.0"},{capabilities:{resources:{listChanged:!0},tools:{listChanged:!0}}});e.setRequestHandler(xe,async()=>({resources:(await $e()).map(n=>({uri:De(n),name:n,description:`loveui registry definition for ${n}`,mimeType:"application/json"}))})),e.setRequestHandler(Te,async()=>({resourceTemplates:[{name:"loveui-registry",uriTemplate:Oe,description:"loveui registry definitions by package name",mimeType:"application/json"}]})),e.setRequestHandler(_e,async s=>{let n=Fe(s.params.uri),r=await X(n);return{contents:[{uri:s.params.uri,mimeType:"application/json",text:JSON.stringify(r,null,2)}]}}),e.setRequestHandler(Ie,async()=>({tools:[{name:B,description:"Fetch a loveui registry definition by package name.",inputSchema:{type:"object",additionalProperties:!1,properties:{name:{type:"string",description:"Package name, e.g. badge"}},required:["name"]}}]})),e.setRequestHandler(Ee,async s=>{if(s.params.name!==B)throw new d(g.InvalidParams,`Tool ${s.params.name} not found`);let n=s.params.arguments?.name;if(typeof n!="string"||n.trim()==="")throw new d(g.InvalidParams,"Package name is required.");let r=await X(n.trim());return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}});let t=new je;await e.connect(t)}Ne().catch(e=>{console.error(e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "love-ui",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "private": false,
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -0,0 +1,62 @@
1
+ # @loveui/code-blocks
2
+
3
+ React code-block components for LoveUI.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @loveui/code-blocks
9
+ ```
10
+
11
+ ## Required Styles
12
+
13
+ Import your project styles and at least one highlighter stylesheet.
14
+
15
+ ```css
16
+ @import "@loveui/code-blocks/styles/globals.css";
17
+ @import "@loveui/code-blocks/styles/shiki.css";
18
+ /* or */
19
+ @import "@loveui/code-blocks/styles/sugar-high.css";
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```tsx
25
+ import {
26
+ CodeBlock,
27
+ CodeBlockHeader,
28
+ CodeBlockContent,
29
+ CodeBlockGroup,
30
+ CodeBlockIcon,
31
+ CopyButton,
32
+ CodeblockShiki,
33
+ } from "@loveui/code-blocks";
34
+
35
+ const code = "console.log('LoveUI');";
36
+
37
+ export function Example() {
38
+ return (
39
+ <CodeBlock>
40
+ <CodeBlockHeader>
41
+ <CodeBlockGroup>
42
+ <CodeBlockIcon language="ts" />
43
+ <span>example.ts</span>
44
+ </CodeBlockGroup>
45
+ <CopyButton content={code} />
46
+ </CodeBlockHeader>
47
+ <CodeBlockContent>
48
+ <CodeblockShiki code={code} language="ts" />
49
+ </CodeBlockContent>
50
+ </CodeBlock>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## Exports
56
+
57
+ - Code block primitives: `CodeBlock`, `CodeBlockHeader`, `CodeBlockContent`, `CodeBlockGroup`, `CodeBlockIcon`
58
+ - Highlighters: `CodeblockShiki`, `CodeBlockSugarHigh`
59
+ - MDX helpers: `PreShikiComponent`, `PreSugarHighComponent`
60
+ - Blocks: `InlineCode`, `MultiTabs`, `CodeBlockSelectPkg`, `CodeBlockTabsPkg`
61
+ - Utilities: `cn`, `copyToClipboard`, `reactToText`, `highlight`, `sugarHighHighlight`
62
+ - Store: `usePackageManager`
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": false,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ }
21
+ }
@@ -1,28 +1,52 @@
1
1
  {
2
- "name": "@loveui/code-block",
3
- "description": "Provides syntax highlighting, line numbers, and copy to clipboard functionality for code blocks.",
2
+ "name": "@loveui/code-blocks",
4
3
  "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "files": [
7
+ "src",
8
+ "components.json",
9
+ "README.md"
10
+ ],
11
+ "sideEffects": [
12
+ "**/*.css"
13
+ ],
14
+ "exports": {
15
+ ".": "./src/index.ts",
16
+ "./components/*": "./src/components/*",
17
+ "./utils/*": "./src/utils/*",
18
+ "./stores/*": "./src/stores/*",
19
+ "./styles/globals.css": "./src/styles/globals.css",
20
+ "./styles/shiki.css": "./src/styles/shiki.css",
21
+ "./styles/sugar-high.css": "./src/styles/sugar-high.css",
22
+ "./components.json": "./components.json"
23
+ },
24
+ "scripts": {
25
+ "check-types": "tsc --noEmit"
26
+ },
5
27
  "dependencies": {
6
- "@radix-ui/react-use-controllable-state": "^1.2.2",
7
- "@loveui/shadcn-ui": "^0.1.0",
8
- "@shikijs/transformers": "^3.13.0",
9
- "lucide-react": "^0.545.0",
10
- "react": "^19.2.0",
11
- "react-dom": "^19.2.0",
12
- "react-icons": "^5.5.0",
13
- "shiki": "3.13.0"
28
+ "@base-ui/react": "1.1.0",
29
+ "@react-symbols/icons": "1.3.0",
30
+ "@shikijs/langs": "3.22.0",
31
+ "@shikijs/themes": "3.22.0",
32
+ "class-variance-authority": "0.7.1",
33
+ "clsx": "2.1.1",
34
+ "lucide-react": "0.563.0",
35
+ "shiki": "3.22.0",
36
+ "sugar-high": "0.9.5",
37
+ "tailwind-merge": "3.4.0",
38
+ "tw-animate-css": "1.4.0",
39
+ "zustand": "5.0.11"
14
40
  },
15
- "devDependencies": {
16
- "@loveui/typescript-config": "^0.1.0",
17
- "@types/react": "^19.2.2",
18
- "@types/react-dom": "^19.2.1",
19
- "typescript": "^5.9.3"
41
+ "peerDependencies": {
42
+ "react": ">=18",
43
+ "react-dom": ">=18",
44
+ "tailwindcss": ">=4"
20
45
  },
21
- "files": [
22
- "dist",
23
- "*.ts",
24
- "*.tsx",
25
- "*.css",
26
- "*.mdx"
27
- ]
46
+ "devDependencies": {
47
+ "@types/mdx": "2.0.13",
48
+ "@types/react": "19.2.10",
49
+ "@types/react-dom": "19.2.3",
50
+ "typescript": "5.9.3"
51
+ }
28
52
  }
@@ -0,0 +1,146 @@
1
+ "use client";
2
+
3
+ import { useState, type FC, type SVGProps } from "react";
4
+ import {
5
+ usePackageManager,
6
+ type PackageManager,
7
+ } from "../../../stores/packageManager";
8
+
9
+ import {
10
+ CodeBlock,
11
+ CodeBlockContent,
12
+ CodeBlockHeader,
13
+ CodeBlockIcon,
14
+ } from "../code-block";
15
+ import { CopyButton } from "../copy-button";
16
+ import { CodeblockShiki } from "../client/shiki";
17
+
18
+ import {
19
+ DropdownMenu,
20
+ DropdownMenuContent,
21
+ DropdownMenuItem,
22
+ DropdownMenuTrigger,
23
+ } from "../../ui/dropdown-menu";
24
+
25
+ import { cn } from "../../../utils/cn";
26
+ import { Bun, NPM, PNPM, Yarn } from "@react-symbols/icons";
27
+ import { CheckIcon, ChevronDownIcon } from "lucide-react";
28
+
29
+ interface CodeBlockSelectPkgProps {
30
+ command: string;
31
+ title: string;
32
+ type: "install" | "dlx";
33
+ }
34
+
35
+ interface Command {
36
+ name: PackageManager;
37
+ install: string;
38
+ icon: FC<SVGProps<SVGSVGElement>>;
39
+ dlx: string;
40
+ }
41
+
42
+ const Commands: Command[] = [
43
+ {
44
+ name: "npm",
45
+ install: "npm i",
46
+ icon: NPM,
47
+ dlx: "npx",
48
+ },
49
+ {
50
+ name: "pnpm",
51
+ install: "pnpm i",
52
+ icon: PNPM,
53
+ dlx: "pnpm dlx",
54
+ },
55
+ {
56
+ name: "yarn",
57
+ install: "yarn add",
58
+ icon: Yarn,
59
+ dlx: "yarn dlx",
60
+ },
61
+ {
62
+ name: "bun",
63
+ install: "bun add",
64
+ icon: Bun,
65
+ dlx: "bunx --bun",
66
+ },
67
+ ];
68
+
69
+ const SelectPackageManager = () => {
70
+ const [isOpen, setIsOpen] = useState(false);
71
+ const { packageManager, setPackageManager } = usePackageManager();
72
+
73
+ const selectedPkg =
74
+ Commands.find((pkg) => pkg.name === packageManager) ?? Commands[0];
75
+ const Icon = selectedPkg.icon;
76
+
77
+ return (
78
+ <DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
79
+ <DropdownMenuTrigger
80
+ title="Select Package Manager"
81
+ className="group flex cursor-pointer items-center space-x-1 px-2"
82
+ >
83
+ <Icon className="size-4" />
84
+ <ChevronDownIcon
85
+ size={13}
86
+ className={cn(
87
+ "transform transition-transform duration-200 ease-in-out",
88
+ "group-hover:text-black dark:group-hover:text-white",
89
+ isOpen && "rotate-180 text-black dark:text-white",
90
+ )}
91
+ />
92
+ </DropdownMenuTrigger>
93
+ <DropdownMenuContent align="end" alignOffset={2}>
94
+ {Commands.map((pkg) => {
95
+ const PkgIcon = pkg.icon;
96
+ return (
97
+ <DropdownMenuItem
98
+ key={pkg.name}
99
+ title={`Using ${pkg.name}`}
100
+ onClick={() => setPackageManager(pkg.name)}
101
+ className="flex w-full items-center justify-between"
102
+ >
103
+ <div className="flex items-center space-x-2">
104
+ <PkgIcon className="size-4" />
105
+ <span>{pkg.name}</span>
106
+ </div>
107
+ {selectedPkg.name === pkg.name && <CheckIcon width={14} />}
108
+ </DropdownMenuItem>
109
+ );
110
+ })}
111
+ </DropdownMenuContent>
112
+ </DropdownMenu>
113
+ );
114
+ };
115
+
116
+ const CodeBlockSelectPkg = ({
117
+ title,
118
+ type,
119
+ command,
120
+ }: CodeBlockSelectPkgProps) => {
121
+ const { packageManager } = usePackageManager();
122
+
123
+ const selectedPkg =
124
+ Commands.find((pkg) => pkg.name === packageManager) ?? Commands[0];
125
+ const fullCommand = `${selectedPkg[type]} ${command}`;
126
+
127
+ return (
128
+ <CodeBlock>
129
+ <CodeBlockHeader>
130
+ <div className="flex items-center space-x-2">
131
+ <CodeBlockIcon language="bash" />
132
+ <span className="font-medium">{title}</span>
133
+ </div>
134
+ <div className="flex items-center space-x-2 divide-x divide-neutral-300 dark:divide-neutral-700">
135
+ <SelectPackageManager />
136
+ <CopyButton className="pl-1" content={fullCommand} />
137
+ </div>
138
+ </CodeBlockHeader>
139
+ <CodeBlockContent>
140
+ <CodeblockShiki language="bash" code={fullCommand} />
141
+ </CodeBlockContent>
142
+ </CodeBlock>
143
+ );
144
+ };
145
+
146
+ export { CodeBlockSelectPkg, SelectPackageManager };
@@ -0,0 +1,110 @@
1
+ "use client";
2
+
3
+ import type { FC, SVGProps } from "react";
4
+ import {
5
+ usePackageManager,
6
+ type PackageManager,
7
+ } from "../../../stores/packageManager";
8
+
9
+ import {
10
+ CodeBlock,
11
+ CodeBlockContent,
12
+ CodeBlockHeader,
13
+ CodeBlockIcon,
14
+ } from "../code-block";
15
+ import { CopyButton } from "../copy-button";
16
+ import { CodeblockShiki } from "../client/shiki";
17
+
18
+ import { Bun, NPM, PNPM, Yarn } from "@react-symbols/icons";
19
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
20
+
21
+ interface Command {
22
+ name: PackageManager;
23
+ install: string;
24
+ icon: FC<SVGProps<SVGSVGElement>>;
25
+ dlx: string;
26
+ }
27
+
28
+ interface CodeBlockTabsPkgProps {
29
+ command: string;
30
+ type: "install" | "dlx";
31
+ }
32
+
33
+ const Commands: Command[] = [
34
+ {
35
+ name: "npm",
36
+ install: "npm i",
37
+ icon: NPM,
38
+ dlx: "npx",
39
+ },
40
+ {
41
+ name: "pnpm",
42
+ install: "pnpm i",
43
+ icon: PNPM,
44
+ dlx: "pnpm dlx",
45
+ },
46
+ {
47
+ name: "yarn",
48
+ install: "yarn add",
49
+ icon: Yarn,
50
+ dlx: "yarn dlx",
51
+ },
52
+ {
53
+ name: "bun",
54
+ install: "bun add",
55
+ icon: Bun,
56
+ dlx: "bunx --bun",
57
+ },
58
+ ];
59
+
60
+ const CodeBlockTabsPkg = ({ command, type }: CodeBlockTabsPkgProps) => {
61
+ const { packageManager, setPackageManager } = usePackageManager();
62
+
63
+ const selectedPkg =
64
+ Commands.find((pkg) => pkg.name === packageManager) ?? Commands[0];
65
+ const fullCommand = `${selectedPkg[type]} ${command}`;
66
+
67
+ return (
68
+ <Tabs
69
+ className="w-full gap-1"
70
+ value={packageManager}
71
+ onValueChange={(value) => setPackageManager(value as PackageManager)}
72
+ >
73
+ <CodeBlock>
74
+ <CodeBlockHeader>
75
+ <div className="flex items-center space-x-1">
76
+ <CodeBlockIcon language="bash" />
77
+ <TabsList className="gap-1 border-0 bg-transparent dark:bg-transparent">
78
+ {Commands.map((cmd) => {
79
+ const Icon = cmd.icon;
80
+ return (
81
+ <TabsTrigger
82
+ key={cmd.name}
83
+ value={cmd.name}
84
+ className="data-[state=active]:shadow-none"
85
+ >
86
+ <Icon className="size-4" />
87
+ <span>{cmd.name}</span>
88
+ </TabsTrigger>
89
+ );
90
+ })}
91
+ </TabsList>
92
+ </div>
93
+ <CopyButton className="pl-1" content={fullCommand} />
94
+ </CodeBlockHeader>
95
+ <CodeBlockContent>
96
+ {Commands.map((cmd) => (
97
+ <TabsContent key={cmd.name} value={cmd.name} className="mt-0">
98
+ <CodeblockShiki
99
+ language="bash"
100
+ code={`${cmd[type]} ${command}`}
101
+ />
102
+ </TabsContent>
103
+ ))}
104
+ </CodeBlockContent>
105
+ </CodeBlock>
106
+ </Tabs>
107
+ );
108
+ };
109
+
110
+ export { CodeBlockTabsPkg };
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import type { Languages } from "../../../utils/shiki/highlight";
4
+
5
+ import {
6
+ CodeBlock,
7
+ CodeBlockContent,
8
+ } from "../code-block";
9
+ import { CopyButton } from "../copy-button";
10
+ import { CodeblockShiki } from "../client/shiki";
11
+
12
+ interface InlineCodeProps {
13
+ code: string;
14
+ language?: Languages;
15
+ }
16
+
17
+ const InlineCode = ({ code, language = "bash" }: InlineCodeProps) => {
18
+ return (
19
+ <CodeBlock>
20
+ <CodeBlockContent className="flex w-full items-center justify-between">
21
+ <CodeblockShiki language={language} code={code} />
22
+ <CopyButton className="px-3" content={code} />
23
+ </CodeBlockContent>
24
+ </CodeBlock>
25
+ );
26
+ };
27
+
28
+ export default InlineCode;
@@ -0,0 +1,118 @@
1
+ import type { Languages } from "../../../utils/shiki/highlight";
2
+
3
+ import {
4
+ CodeBlock,
5
+ CodeBlockContent,
6
+ CodeBlockHeader,
7
+ CodeBlockIcon,
8
+ } from "../code-block";
9
+ import { CopyButton } from "../copy-button";
10
+ import { CodeblockShiki } from "../client/shiki";
11
+
12
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
13
+
14
+ const Code = [
15
+ {
16
+ title: "layout.tsx",
17
+ lang: "tsx",
18
+ code: `import "./globals.css";
19
+
20
+ export default function RootLayout({
21
+ children,
22
+ }: Readonly<{
23
+ children: React.ReactNode;
24
+ }>) {
25
+ return (
26
+ <html lang="en">
27
+ <body>{children}</body>
28
+ </html>
29
+ );
30
+ }`,
31
+ },
32
+ {
33
+ title: "page.tsx",
34
+ lang: "tsx",
35
+ code: `export default function Home() {
36
+ return (
37
+ <div>
38
+ <p>Home page</p>
39
+ </div>
40
+ );
41
+ }`,
42
+ },
43
+ {
44
+ title: "globals.css",
45
+ lang: "css",
46
+ code: `@import "tailwindcss";
47
+
48
+ :root {
49
+ --background: #ffffff;
50
+ --foreground: #171717;
51
+ }
52
+
53
+ @theme inline {
54
+ --color-background: var(--background);
55
+ --color-foreground: var(--foreground);
56
+ --font-sans: var(--font-geist-sans);
57
+ --font-mono: var(--font-geist-mono);
58
+ }
59
+
60
+ @media (prefers-color-scheme: dark) {
61
+ :root {
62
+ --background: #0a0a0a;
63
+ --foreground: #ededed;
64
+ }
65
+ }
66
+
67
+ body {
68
+ background: var(--background);
69
+ color: var(--foreground);
70
+ font-family: Arial, Helvetica, sans-serif;
71
+ }`,
72
+ },
73
+ ];
74
+
75
+ const CopyWithTabsCode = () => {
76
+ return (
77
+ <Tabs className="w-full gap-1">
78
+ <CodeBlock>
79
+ <CodeBlockHeader>
80
+ <div className="flex items-center space-x-1">
81
+ <TabsList className="gap-1 border-0 bg-transparent dark:bg-transparent">
82
+ {Code.map((c) => {
83
+ return (
84
+ <TabsTrigger
85
+ value={c.title}
86
+ key={c.title}
87
+ className="data-[state=active]:bg-transparent"
88
+ >
89
+ <CodeBlockIcon language={c.lang} />
90
+ <span>{c.title}</span>
91
+ </TabsTrigger>
92
+ );
93
+ })}
94
+ </TabsList>
95
+ </div>
96
+ </CodeBlockHeader>
97
+ <CodeBlockContent className="relative">
98
+ {Code.map((cmd) => (
99
+ <TabsContent key={cmd.title} value={cmd.title}>
100
+ <div className="relative">
101
+ <CopyButton
102
+ content={cmd.code}
103
+ className="sticky top-2.5 right-2.5 z-10 float-right -mb-10 p-1"
104
+ />
105
+ <CodeblockShiki
106
+ code={cmd.code}
107
+ language={cmd.lang as Languages}
108
+ />
109
+ </div>
110
+ </TabsContent>
111
+ ))}
112
+ </CodeBlockContent>
113
+ </CodeBlock>
114
+ </Tabs>
115
+ );
116
+ };
117
+
118
+ export default CopyWithTabsCode;