@particle-academy/fancy-code 0.5.1 → 0.6.0

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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm install @particle-academy/fancy-code
12
12
  # or: yarn add @particle-academy/fancy-code
13
13
  ```
14
14
 
15
- **Peer dependencies:** `react >= 18`, `react-dom >= 18`, `@particle-academy/react-fancy >= 1.5`
15
+ **Peer dependencies:** `react >= 18`, `react-dom >= 18`, `@particle-academy/react-fancy >= 4.9` (the `FileViewer` delegates media to react-fancy's `<MediaViewer>`, added in 4.9)
16
16
 
17
17
  ## Setup
18
18
 
@@ -44,11 +44,38 @@ function App() {
44
44
 
45
45
  The component ships with no default border or rounding — add your own via `className` for standalone use, or embed directly into IDE layouts without visual conflicts.
46
46
 
47
+ ## Unified file viewer
48
+
49
+ Showing arbitrary files from a tree? `FileViewer` renders text in the
50
+ `CodeEditor` and delegates **media** (image / video / audio / PDF) to
51
+ react-fancy's `<MediaViewer>` — so an image no longer renders as binary text.
52
+
53
+ ```tsx
54
+ import { FileViewer } from "@particle-academy/fancy-code";
55
+
56
+ // text → CodeEditor (read-only, language picked from the filename)
57
+ <FileViewer filename="app.tsx" value={source} />
58
+
59
+ // media → <MediaViewer> (pass the file URL as `src`)
60
+ <FileViewer filename="logo.png" src={url} style={{ height: 360 }} />
61
+ ```
62
+
63
+ Need to branch your own chrome (tabs, save buttons)? Call `resolveFileKind`
64
+ yourself — it's the same text-vs-media decision `FileViewer` makes internally:
65
+
66
+ ```tsx
67
+ import { resolveFileKind } from "@particle-academy/fancy-code";
68
+
69
+ resolveFileKind({ filename: "logo.png" }); // { kind: "media", mediaKind: "image" }
70
+ resolveFileKind({ filename: "app.tsx" }); // { kind: "text", language: "typescript" }
71
+ ```
72
+
47
73
  ## Documentation
48
74
 
49
75
  | Topic | Doc |
50
76
  |-------|-----|
51
77
  | Full component API (props, sub-components, `useCodeEditor` hook) | [docs/CodeEditor.md](./docs/CodeEditor.md) |
78
+ | Unified file viewer (text + media), `resolveFileKind` | [docs/FileViewer.md](./docs/FileViewer.md) |
52
79
  | Built-in languages + registering custom ones | [docs/languages.md](./docs/languages.md) |
53
80
  | Built-in themes + custom theme registration | [docs/themes.md](./docs/themes.md) |
54
81
 
package/dist/index.cjs CHANGED
@@ -1833,6 +1833,118 @@ function MarkdownEditor({
1833
1833
  );
1834
1834
  }
1835
1835
  MarkdownEditor.displayName = "MarkdownEditor";
1836
+ var EXT_LANGUAGE = {
1837
+ js: "javascript",
1838
+ mjs: "javascript",
1839
+ cjs: "javascript",
1840
+ jsx: "javascript",
1841
+ json: "javascript",
1842
+ // the JS tokenizer highlights JSON acceptably
1843
+ jsonc: "javascript",
1844
+ ts: "typescript",
1845
+ mts: "typescript",
1846
+ cts: "typescript",
1847
+ tsx: "typescript",
1848
+ html: "html",
1849
+ htm: "html",
1850
+ xml: "html",
1851
+ xhtml: "html",
1852
+ vue: "html",
1853
+ php: "php",
1854
+ phtml: "php",
1855
+ py: "python",
1856
+ pyw: "python",
1857
+ go: "go",
1858
+ md: "markdown",
1859
+ markdown: "markdown",
1860
+ mkd: "markdown",
1861
+ mdx: "markdown"
1862
+ };
1863
+ function basename(filename) {
1864
+ return filename.split(/[?#]/, 1)[0].split(/[\\/]/).pop() ?? "";
1865
+ }
1866
+ function languageFromFilename(filename) {
1867
+ if (!filename) return "plaintext";
1868
+ const seg = basename(filename);
1869
+ const dot = seg.lastIndexOf(".");
1870
+ if (dot <= 0) return "plaintext";
1871
+ const ext = seg.slice(dot + 1).toLowerCase();
1872
+ return EXT_LANGUAGE[ext] ?? "plaintext";
1873
+ }
1874
+ function resolveFileKind({ filename, mime }) {
1875
+ const media = reactFancy.resolveMediaType({ mime, src: filename });
1876
+ if (media !== "unknown") return { kind: "media", mediaKind: media };
1877
+ return { kind: "text", language: languageFromFilename(filename) };
1878
+ }
1879
+ function FileViewer({
1880
+ filename,
1881
+ mime,
1882
+ src,
1883
+ value = "",
1884
+ onChange,
1885
+ readOnly = true,
1886
+ theme = "auto",
1887
+ fileKind,
1888
+ lineNumbers = true,
1889
+ wordWrap = false,
1890
+ minHeight,
1891
+ maxHeight,
1892
+ mediaProps,
1893
+ className,
1894
+ style
1895
+ }) {
1896
+ const resolved = fileKind ?? resolveFileKind({ filename, mime });
1897
+ if (resolved.kind === "media") {
1898
+ const { className: mediaClassName, ...restMediaProps } = mediaProps ?? {};
1899
+ const fill = resolved.mediaKind !== "audio";
1900
+ return /* @__PURE__ */ jsxRuntime.jsx(
1901
+ "div",
1902
+ {
1903
+ "data-fancy-file-viewer": "",
1904
+ "data-kind": "media",
1905
+ "data-media-kind": resolved.mediaKind,
1906
+ className: reactFancy.cn("overflow-hidden", className),
1907
+ style,
1908
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1909
+ reactFancy.MediaViewer,
1910
+ {
1911
+ src: src ?? "",
1912
+ mime,
1913
+ alt: filename,
1914
+ kind: resolved.mediaKind,
1915
+ className: reactFancy.cn(fill && "h-full w-full", mediaClassName),
1916
+ ...restMediaProps
1917
+ }
1918
+ )
1919
+ }
1920
+ );
1921
+ }
1922
+ return /* @__PURE__ */ jsxRuntime.jsx(
1923
+ "div",
1924
+ {
1925
+ "data-fancy-file-viewer": "",
1926
+ "data-kind": "text",
1927
+ className,
1928
+ style,
1929
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1930
+ CodeEditor,
1931
+ {
1932
+ value,
1933
+ onChange: readOnly ? void 0 : onChange,
1934
+ language: resolved.language,
1935
+ theme,
1936
+ readOnly,
1937
+ lineNumbers,
1938
+ wordWrap,
1939
+ minHeight,
1940
+ maxHeight,
1941
+ children: /* @__PURE__ */ jsxRuntime.jsx(CodeEditor.Panel, {})
1942
+ }
1943
+ )
1944
+ }
1945
+ );
1946
+ }
1947
+ FileViewer.displayName = "FileViewer";
1836
1948
  function useCodeEditorInputTagAdapter() {
1837
1949
  const ctx = useCodeEditor();
1838
1950
  return react.useMemo(() => reactFancy.textareaAdapter(ctx.textareaRef), [ctx.textareaRef]);
@@ -1844,14 +1956,17 @@ function CodeEditorInputTag(props) {
1844
1956
 
1845
1957
  exports.CodeEditor = CodeEditor;
1846
1958
  exports.CodeEditorInputTag = CodeEditorInputTag;
1959
+ exports.FileViewer = FileViewer;
1847
1960
  exports.MarkdownEditor = MarkdownEditor;
1848
1961
  exports.getLanguage = getLanguage;
1849
1962
  exports.getRegisteredLanguages = getRegisteredLanguages;
1850
1963
  exports.getRegisteredThemes = getRegisteredThemes;
1851
1964
  exports.getTheme = getTheme;
1965
+ exports.languageFromFilename = languageFromFilename;
1852
1966
  exports.registerLanguage = registerLanguage;
1853
1967
  exports.registerTheme = registerTheme;
1854
1968
  exports.renderMarkdown = renderMarkdown;
1969
+ exports.resolveFileKind = resolveFileKind;
1855
1970
  exports.tokenizeMarkdown = tokenizeMarkdown;
1856
1971
  exports.useCodeEditor = useCodeEditor;
1857
1972
  exports.useCodeEditorInputTagAdapter = useCodeEditorInputTagAdapter;