@ekkolyth/ui 0.1.19 → 0.2.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/dist/layout.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as Container } from "./index-Bwp0YMmG.js";
2
- export { Container };
1
+ import { n as VirtualGridProps, r as Container, t as VirtualGrid } from "./index-CxGQ12zb.js";
2
+ export { Container, VirtualGrid, type VirtualGridProps };
package/dist/layout.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as Container } from "./container-BBg21poC.js";
2
- export { Container };
1
+ import { n as Container, t as VirtualGrid } from "./virtual-grid-BNWXDxO1.js";
2
+ export { Container, VirtualGrid };
@@ -0,0 +1,46 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { codeToTokens } from "shiki";
4
+ //#region src/components/rich-text/shiki-code.tsx
5
+ function parseShikiStyle(style) {
6
+ const variables = {};
7
+ for (const declaration of style.split(";")) {
8
+ const [property, value] = declaration.split(":");
9
+ if (property?.startsWith("--") && value) variables[property] = value;
10
+ }
11
+ return variables;
12
+ }
13
+ function ShikiCode({ code, language }) {
14
+ const [highlighted, setHighlighted] = useState(null);
15
+ useEffect(() => {
16
+ let cancelled = false;
17
+ const highlight = (lang) => codeToTokens(code, {
18
+ lang,
19
+ themes: {
20
+ light: "github-light",
21
+ dark: "github-dark"
22
+ },
23
+ defaultColor: false
24
+ });
25
+ highlight(language === "text" ? "plaintext" : language).catch(() => highlight("plaintext")).then((out) => {
26
+ if (!cancelled) setHighlighted(out);
27
+ }, () => {});
28
+ return () => {
29
+ cancelled = true;
30
+ };
31
+ }, [code, language]);
32
+ if (!highlighted) return null;
33
+ return /* @__PURE__ */ jsx("pre", {
34
+ className: `shiki ${highlighted.themeName} rounded bg-muted p-3 text-sm`,
35
+ style: parseShikiStyle(highlighted.rootStyle || ""),
36
+ children: /* @__PURE__ */ jsx("code", { children: highlighted.tokens.map((line, lineIndex) => /* @__PURE__ */ jsxs("span", {
37
+ className: "line",
38
+ children: [line.map((token) => /* @__PURE__ */ jsx("span", {
39
+ style: token.htmlStyle,
40
+ children: token.content
41
+ }, token.offset)), lineIndex < highlighted.tokens.length - 1 && "\n"]
42
+ }, line.map(({ content, offset }) => `${offset}:${content}`).join("|"))) })
43
+ });
44
+ }
45
+ //#endregion
46
+ export { ShikiCode as default };
package/dist/text.d.ts CHANGED
@@ -14,4 +14,11 @@ interface RichTextProps {
14
14
  //#region src/components/rich-text/index.d.ts
15
15
  declare function RichText({ json, nodes }: RichTextProps): import("react").JSX.Element;
16
16
  //#endregion
17
- export { type NodeRegistry, type NodeRenderer, RichText, type RichTextProps };
17
+ //#region src/components/rich-text/code-block.d.ts
18
+ interface CodeBlockProps {
19
+ code: string;
20
+ language: string;
21
+ }
22
+ declare function CodeBlock({ code, language }: CodeBlockProps): import("react").JSX.Element;
23
+ //#endregion
24
+ export { CodeBlock, type NodeRegistry, type NodeRenderer, RichText, type RichTextProps };
package/dist/text.js CHANGED
@@ -1,7 +1,50 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Fragment as Fragment$1, Suspense, lazy } from "react";
2
+ import { Fragment as Fragment$1, Suspense, lazy, useEffect, useRef, useState } from "react";
3
+ //#region src/components/rich-text/code-block.tsx
4
+ const ShikiCode = lazy(() => import("./shiki-code-CLD9RZfj.js"));
5
+ function CodeBlockRenderer({ code, language }) {
6
+ const outputRef = useRef(null);
7
+ const [isHighlighted, setIsHighlighted] = useState(false);
8
+ useEffect(() => {
9
+ const output = outputRef.current;
10
+ if (!output) return;
11
+ const showHighlightedOutput = () => {
12
+ if (output.firstElementChild?.hasChildNodes()) setIsHighlighted(true);
13
+ };
14
+ showHighlightedOutput();
15
+ const observer = new MutationObserver(showHighlightedOutput);
16
+ observer.observe(output, {
17
+ childList: true,
18
+ subtree: true
19
+ });
20
+ return () => observer.disconnect();
21
+ }, []);
22
+ return /* @__PURE__ */ jsxs("div", {
23
+ className: "w-full min-w-0 overflow-x-auto",
24
+ children: [isHighlighted ? null : /* @__PURE__ */ jsx("pre", {
25
+ className: "rounded bg-muted p-3 text-sm",
26
+ children: code
27
+ }), /* @__PURE__ */ jsx("div", {
28
+ hidden: !isHighlighted,
29
+ ref: outputRef,
30
+ children: /* @__PURE__ */ jsx(Suspense, {
31
+ fallback: null,
32
+ children: /* @__PURE__ */ jsx(ShikiCode, {
33
+ code,
34
+ language
35
+ })
36
+ })
37
+ })]
38
+ });
39
+ }
40
+ function CodeBlock({ code, language }) {
41
+ return /* @__PURE__ */ jsx(CodeBlockRenderer, {
42
+ code,
43
+ language
44
+ }, `${language}\0${code}`);
45
+ }
46
+ //#endregion
3
47
  //#region src/components/rich-text/nodes.tsx
4
- const ShikiCode = lazy(() => import("./shiki-code-Bg__Vnt1.js"));
5
48
  const coreNodes = {
6
49
  doc: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children }),
7
50
  paragraph: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
@@ -68,15 +111,9 @@ const coreNodes = {
68
111
  }
69
112
  return /* @__PURE__ */ jsx("span", { children: out });
70
113
  },
71
- codeBlock: ({ node }) => /* @__PURE__ */ jsx(Suspense, {
72
- fallback: /* @__PURE__ */ jsx("pre", {
73
- className: "rounded bg-muted p-3 text-sm",
74
- children: node.attrs?.code
75
- }),
76
- children: /* @__PURE__ */ jsx(ShikiCode, {
77
- code: node.attrs?.code ?? "",
78
- language: node.attrs?.language ?? "text"
79
- })
114
+ codeBlock: ({ node }) => /* @__PURE__ */ jsx(CodeBlock, {
115
+ code: node.attrs?.code ?? "",
116
+ language: node.attrs?.language ?? "text"
80
117
  }),
81
118
  callout: ({ children }) => /* @__PURE__ */ jsx("aside", {
82
119
  className: "my-3 rounded-md border-l-4 border-l-info bg-info/10 px-3 py-2",
@@ -174,4 +211,4 @@ function renderNode(node, key, registry) {
174
211
  }, key);
175
212
  }
176
213
  //#endregion
177
- export { RichText };
214
+ export { CodeBlock, RichText };
@@ -41,6 +41,7 @@ interface ThemeProviderProps {
41
41
  children?: ReactNode;
42
42
  darkTheme?: DarkThemes | DarkThemes[];
43
43
  defaultAppearance?: Appearance;
44
+ forcedAppearance?: 'light' | 'dark';
44
45
  lightTheme?: LightThemes | LightThemes[];
45
46
  storageKey?: string;
46
47
  }
@@ -48,7 +49,7 @@ declare function useTheme(): ThemeContextState;
48
49
  declare function useThemeContext(): ThemeContextState | null;
49
50
  //#endregion
50
51
  //#region src/components/themes/provider.d.ts
51
- declare function ThemeProvider({ children, darkTheme, defaultAppearance, lightTheme, storageKey }: PropsWithChildren<ThemeProviderProps>): import("react").JSX.Element;
52
+ declare function ThemeProvider({ children, darkTheme, defaultAppearance, forcedAppearance, lightTheme, storageKey }: PropsWithChildren<ThemeProviderProps>): import("react").JSX.Element;
52
53
  //#endregion
53
54
  //#region src/components/themes/theme-script.d.ts
54
55
  interface ThemeScriptProps {
@@ -1,8 +1,8 @@
1
1
  import { t as cn } from "../utils-C_uf36nf.js";
2
- import { _ as SelectTrigger, a as applyTheme, c as useThemeContext, d as SelectGroup, f as SelectItem, i as ThemeContext, l as Select, o as normalizeMode, p as SelectLabel, r as Toaster, s as useTheme, t as Toggle, u as SelectContent, v as SelectValue } from "../toggle-B2PFPImC.js";
2
+ import { _ as SelectTrigger, a as applyTheme, c as useThemeContext, d as SelectGroup, f as SelectItem, i as ThemeContext, l as Select, o as normalizeMode, p as SelectLabel, r as Toaster, s as useTheme, t as Toggle, u as SelectContent, v as SelectValue } from "../toggle-Bpm-ggNe.js";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { Monitor, Moon, Sun } from "lucide-react";
5
4
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { Monitor, Moon, Sun } from "lucide-react";
6
6
  //#region src/lib/types.ts
7
7
  const APPEARANCE = [
8
8
  "light",
@@ -137,7 +137,7 @@ function systemPrefersDark() {
137
137
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
138
138
  return window.matchMedia("(prefers-color-scheme: dark)").matches;
139
139
  }
140
- function ThemeProvider({ children, darkTheme = ["ekkolyth-dark"], defaultAppearance = "system", lightTheme = ["ekkolyth-light"], storageKey = "ekko-ui" }) {
140
+ function ThemeProvider({ children, darkTheme = ["ekkolyth-dark"], defaultAppearance = "system", forcedAppearance, lightTheme = ["ekkolyth-light"], storageKey = "ekko-ui" }) {
141
141
  const lightThemes = useMemo(() => Array.isArray(lightTheme) ? lightTheme : [lightTheme], [lightTheme]);
142
142
  const darkThemes = useMemo(() => Array.isArray(darkTheme) ? darkTheme : [darkTheme], [darkTheme]);
143
143
  const [appearance, setAppearanceState] = useState(defaultAppearance);
@@ -145,7 +145,7 @@ function ThemeProvider({ children, darkTheme = ["ekkolyth-dark"], defaultAppeara
145
145
  const [darkName, setDarkName] = useState(darkThemes[0]);
146
146
  const [systemIsDark, setSystemIsDark] = useState(false);
147
147
  const [hasRestored, setHasRestored] = useState(false);
148
- const normalizedAppearance = normalizeMode(appearance);
148
+ const normalizedAppearance = normalizeMode(forcedAppearance ?? appearance);
149
149
  const resolvedMode = normalizedAppearance === "system" ? systemIsDark ? "dark" : "light" : normalizedAppearance;
150
150
  const resolvedTheme = resolvedMode === "light" ? lightName : darkName;
151
151
  const restoring = useRef(false);
@@ -1,8 +1,8 @@
1
1
  import { t as cn } from "./utils-C_uf36nf.js";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { cva } from "class-variance-authority";
4
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
5
4
  import { createContext, use } from "react";
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
6
6
  import * as SelectPrimitive from "@radix-ui/react-select";
7
7
  import { Toaster } from "sonner";
8
8
  import * as TogglePrimitive from "@radix-ui/react-toggle";
@@ -105,6 +105,7 @@ function applyTheme(theme, mode) {
105
105
  const root = document.documentElement;
106
106
  root.setAttribute("data-theme", theme);
107
107
  root.classList.toggle("dark", mode === "dark");
108
+ root.style.colorScheme = mode;
108
109
  }
109
110
  function useTheme() {
110
111
  const context = use(ThemeContext);
@@ -1,17 +1,17 @@
1
1
  import * as React from "react";
2
2
  //#region src/hooks/use-mobile.ts
3
3
  const MOBILE_BREAKPOINT = 768;
4
- function useIsMobile() {
4
+ function useIsMobile(breakpoint = MOBILE_BREAKPOINT) {
5
5
  const [isMobile, setIsMobile] = React.useState(void 0);
6
6
  React.useEffect(() => {
7
- const mql = window.matchMedia(`(max-width: 767px)`);
7
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
8
8
  const onChange = () => {
9
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9
+ setIsMobile(window.innerWidth < breakpoint);
10
10
  };
11
11
  mql.addEventListener("change", onChange);
12
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ setIsMobile(window.innerWidth < breakpoint);
13
13
  return () => mql.removeEventListener("change", onChange);
14
- }, []);
14
+ }, [breakpoint]);
15
15
  return !!isMobile;
16
16
  }
17
17
  //#endregion
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ //#region src/hooks/use-window-scroll-margin.ts
3
+ function useWindowScrollMargin() {
4
+ const ref = React.useRef(null);
5
+ const [margin, setMargin] = React.useState(0);
6
+ React.useLayoutEffect(() => {
7
+ const element = ref.current;
8
+ if (!element) return;
9
+ const measure = () => setMargin(element.getBoundingClientRect().top + window.scrollY);
10
+ measure();
11
+ const observer = new ResizeObserver(measure);
12
+ observer.observe(document.body);
13
+ observer.observe(element);
14
+ return () => observer.disconnect();
15
+ }, []);
16
+ return {
17
+ ref,
18
+ margin
19
+ };
20
+ }
21
+ //#endregion
22
+ export { useWindowScrollMargin as t };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ //#region src/hooks/use-mobile.d.ts
3
+ declare function useIsMobile(breakpoint?: number): boolean;
4
+ //#endregion
5
+ //#region src/hooks/use-window-scroll-margin.d.ts
6
+ declare function useWindowScrollMargin<T extends HTMLElement>(): {
7
+ ref: React.RefObject<T | null>;
8
+ margin: number;
9
+ };
10
+ //#endregion
11
+ export { useIsMobile as n, useWindowScrollMargin as t };
@@ -0,0 +1,72 @@
1
+ import { t as cn } from "./utils-C_uf36nf.js";
2
+ import { t as useWindowScrollMargin } from "./use-window-scroll-margin-BQMpLEeT.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import * as React from "react";
5
+ import { useWindowVirtualizer } from "@tanstack/react-virtual";
6
+ //#region src/components/layout/container/index.tsx
7
+ function Container({ className, ...props }) {
8
+ return /* @__PURE__ */ jsx("div", {
9
+ className: ["mx-auto flex w-full max-w-7xl flex-1 flex-col p-3 md:p-6", className].filter(Boolean).join(" "),
10
+ ...props
11
+ });
12
+ }
13
+ //#endregion
14
+ //#region src/components/layout/virtual-grid/index.tsx
15
+ function readLayout(element) {
16
+ const computed = getComputedStyle(element);
17
+ const tracks = computed.gridTemplateColumns.split(" ").filter(Boolean);
18
+ return {
19
+ columns: Math.max(tracks.length, 1),
20
+ columnWidth: Number.parseFloat(tracks[0] ?? "") || 0,
21
+ rowGap: Number.parseFloat(computed.rowGap) || 0
22
+ };
23
+ }
24
+ function VirtualGrid({ items, getKey, renderItem, estimateRowHeight, className, style, overscan = 2 }) {
25
+ const { ref, margin } = useWindowScrollMargin();
26
+ const [layout, setLayout] = React.useState({
27
+ columns: 1,
28
+ columnWidth: 0,
29
+ rowGap: 0
30
+ });
31
+ const measureLayout = React.useCallback(() => {
32
+ if (!ref.current) return;
33
+ const next = readLayout(ref.current);
34
+ setLayout((previous) => previous.columns === next.columns && previous.columnWidth === next.columnWidth && previous.rowGap === next.rowGap ? previous : next);
35
+ }, [ref]);
36
+ React.useLayoutEffect(() => {
37
+ if (!ref.current) return;
38
+ const observer = new ResizeObserver(measureLayout);
39
+ observer.observe(ref.current);
40
+ return () => observer.disconnect();
41
+ }, [ref, measureLayout]);
42
+ React.useLayoutEffect(measureLayout);
43
+ const { columns } = layout;
44
+ const virtualizer = useWindowVirtualizer({
45
+ count: Math.ceil(items.length / columns),
46
+ estimateSize: () => estimateRowHeight(layout.columnWidth),
47
+ gap: layout.rowGap,
48
+ getItemKey: (index) => `${columns}:${index}`,
49
+ overscan,
50
+ scrollMargin: margin
51
+ });
52
+ return /* @__PURE__ */ jsx("div", {
53
+ className: cn("relative", className),
54
+ ref,
55
+ style: {
56
+ ...style,
57
+ height: virtualizer.getTotalSize()
58
+ },
59
+ children: virtualizer.getVirtualItems().map((row) => /* @__PURE__ */ jsx("div", {
60
+ className: cn("absolute inset-x-0 top-0", className),
61
+ "data-index": row.index,
62
+ ref: virtualizer.measureElement,
63
+ style: {
64
+ ...style,
65
+ transform: `translateY(${row.start - virtualizer.options.scrollMargin}px)`
66
+ },
67
+ children: items.slice(row.index * columns, (row.index + 1) * columns).map((item, offset) => /* @__PURE__ */ jsx(React.Fragment, { children: renderItem(item, row.index * columns + offset) }, getKey(item)))
68
+ }, row.key))
69
+ });
70
+ }
71
+ //#endregion
72
+ export { Container as n, VirtualGrid as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekkolyth/ui",
3
- "version": "0.1.19",
3
+ "version": "0.2.0",
4
4
  "description": "Shared UI Library for my personal projects",
5
5
  "keywords": [
6
6
  "theme",
@@ -93,9 +93,11 @@
93
93
  "@radix-ui/react-toggle-group": "^1.1.11",
94
94
  "@radix-ui/react-tooltip": "^1.2.8",
95
95
  "@tailwindcss/typography": "^0.5.19",
96
+ "@tanstack/charts": "1.1.0",
96
97
  "@tanstack/react-form": "^1.28.3",
97
98
  "@tanstack/react-pacer": "^0.20.0",
98
99
  "@tanstack/react-table": "^8.21.3",
100
+ "@tanstack/react-virtual": "^3.14.22",
99
101
  "@tiptap/core": "^3.27.1",
100
102
  "class-variance-authority": "^0.7.1",
101
103
  "clsx": "^2.1.1",
@@ -1,10 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- //#region src/components/layout/container/index.tsx
3
- function Container({ className, ...props }) {
4
- return /* @__PURE__ */ jsx("div", {
5
- className: ["mx-auto flex w-full max-w-7xl flex-1 flex-col p-3 md:p-6", className].filter(Boolean).join(" "),
6
- ...props
7
- });
8
- }
9
- //#endregion
10
- export { Container as t };
@@ -1,5 +0,0 @@
1
- import * as React from "react";
2
- //#region src/components/layout/container/index.d.ts
3
- declare function Container({ className, ...props }: React.ComponentProps<'div'>): React.JSX.Element;
4
- //#endregion
5
- export { Container as t };
@@ -1,29 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { codeToHtml } from "shiki";
4
- //#region src/components/rich-text/shiki-code.tsx
5
- function ShikiCode({ code, language }) {
6
- const [html, setHtml] = useState("");
7
- useEffect(() => {
8
- let cancelled = false;
9
- codeToHtml(code, {
10
- lang: language === "text" ? "plaintext" : language,
11
- themes: {
12
- light: "github-light",
13
- dark: "github-dark"
14
- },
15
- defaultColor: false
16
- }).then((out) => {
17
- if (!cancelled) setHtml(out);
18
- });
19
- return () => {
20
- cancelled = true;
21
- };
22
- }, [code, language]);
23
- return /* @__PURE__ */ jsx("div", {
24
- className: "rounded bg-muted p-3 text-sm",
25
- dangerouslySetInnerHTML: { __html: html }
26
- });
27
- }
28
- //#endregion
29
- export { ShikiCode as default };
@@ -1,4 +0,0 @@
1
- //#region src/hooks/use-mobile.d.ts
2
- declare function useIsMobile(): boolean;
3
- //#endregion
4
- export { useIsMobile as t };
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Loader2 } from "lucide-react";
3
2
  import { useEffect, useState } from "react";
3
+ import { Loader2 } from "lucide-react";
4
4
  //#region src/components/blocks/loading-screen/messages.json
5
5
  var messages_default = [
6
6
  "Charging ultimate abilities...",