@optilogic/core 1.0.0-beta.9 → 1.1.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.
Files changed (58) hide show
  1. package/dist/index.cjs +1385 -45
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +360 -1
  4. package/dist/index.d.ts +360 -1
  5. package/dist/index.js +1364 -47
  6. package/dist/index.js.map +1 -1
  7. package/dist/styles.css +22 -0
  8. package/dist/tailwind-preset.cjs +17 -2
  9. package/dist/tailwind-preset.cjs.map +1 -1
  10. package/dist/tailwind-preset.js +17 -2
  11. package/dist/tailwind-preset.js.map +1 -1
  12. package/package.json +15 -1
  13. package/src/components/autocomplete.tsx +2 -1
  14. package/src/components/branding/CosmicFrogIcon.tsx +59 -0
  15. package/src/components/branding/DataStarIcon.tsx +35 -0
  16. package/src/components/branding/OptilogicLogo.tsx +88 -0
  17. package/src/components/branding/OptilogicLogoWithText.tsx +110 -0
  18. package/src/components/branding/index.ts +7 -0
  19. package/src/components/button.tsx +10 -8
  20. package/src/components/calendar.tsx +7 -7
  21. package/src/components/data-grid/DataGrid.tsx +6 -1
  22. package/src/components/data-grid/components/CellEditor.tsx +3 -3
  23. package/src/components/data-grid/hooks/useDataGridState.ts +18 -3
  24. package/src/components/data-grid/types.ts +4 -0
  25. package/src/components/data-grid/utils/dataProcessing.ts +40 -11
  26. package/src/components/date-picker.tsx +2 -1
  27. package/src/components/dropdown-menu.tsx +1 -1
  28. package/src/components/file-view/FileView.tsx +147 -0
  29. package/src/components/file-view/components/CodeRenderer.tsx +97 -0
  30. package/src/components/file-view/components/CsvRenderer.tsx +127 -0
  31. package/src/components/file-view/components/HtmlRenderer.tsx +24 -0
  32. package/src/components/file-view/components/ImageRenderer.tsx +67 -0
  33. package/src/components/file-view/components/MarkdownRenderer.tsx +304 -0
  34. package/src/components/file-view/components/PlainTextRenderer.tsx +27 -0
  35. package/src/components/file-view/components/index.ts +4 -0
  36. package/src/components/file-view/hooks/index.ts +5 -0
  37. package/src/components/file-view/hooks/useContentType.ts +34 -0
  38. package/src/components/file-view/hooks/useDarkMode.ts +62 -0
  39. package/src/components/file-view/hooks/useHighlightedTokens.ts +83 -0
  40. package/src/components/file-view/hooks/useShikiHighlighter.ts +69 -0
  41. package/src/components/file-view/index.ts +47 -0
  42. package/src/components/file-view/types.ts +180 -0
  43. package/src/components/file-view/utils/contentTypeDetection.ts +157 -0
  44. package/src/components/file-view/utils/index.ts +12 -0
  45. package/src/components/file-view/utils/languageMapping.ts +78 -0
  46. package/src/components/file-view/utils/rendererRegistry.ts +42 -0
  47. package/src/components/input.tsx +1 -1
  48. package/src/components/popover.tsx +1 -1
  49. package/src/components/select.tsx +1 -1
  50. package/src/components/switch.tsx +5 -3
  51. package/src/components/textarea.tsx +1 -1
  52. package/src/index.ts +51 -0
  53. package/src/styles.css +22 -0
  54. package/src/tailwind-preset.ts +17 -1
  55. package/src/theme/index.ts +5 -0
  56. package/src/theme/presets.ts +112 -2
  57. package/src/theme/types.ts +35 -0
  58. package/src/theme/utils.ts +231 -0
@@ -0,0 +1,180 @@
1
+ import type * as React from "react";
2
+
3
+ // ============ CONTENT TYPES ============
4
+
5
+ /**
6
+ * Built-in content types that FileView can render.
7
+ */
8
+ export type BuiltInContentType =
9
+ | "code"
10
+ | "markdown"
11
+ | "image"
12
+ | "pdf"
13
+ | "csv"
14
+ | "html"
15
+ | "plaintext"
16
+ | "unknown";
17
+
18
+ /**
19
+ * Content type - either a built-in type or a custom string.
20
+ * The `(string & {})` pattern preserves autocomplete for built-in types
21
+ * while accepting arbitrary strings for custom renderers.
22
+ */
23
+ export type ContentType = BuiltInContentType | (string & {});
24
+
25
+ // ============ IMAGE RESOLUTION ============
26
+
27
+ /**
28
+ * Callback to resolve image `src` values to renderable URLs.
29
+ * Called for non-remote, non-data-URI image sources in markdown content.
30
+ * Can return a blob URL, data URL, or any string that works as an `<img>` src.
31
+ */
32
+ export type ResolveImageUrl = (src: string) => Promise<string> | string;
33
+
34
+ // ============ RENDERER TYPES ============
35
+
36
+ /**
37
+ * Props passed to every renderer component.
38
+ * All renderers receive a consistent contract regardless of content type.
39
+ */
40
+ export interface FileRendererProps {
41
+ /** Text content of the file. Null when content is binary/URL-based. */
42
+ content: string | null;
43
+ /** URL for binary content (images, PDFs). Null for text-based content. */
44
+ url: string | null;
45
+ /** The file name, used for display and language detection. */
46
+ fileName: string;
47
+ /** The resolved content type. */
48
+ contentType: ContentType;
49
+ /** Optional className for the renderer container. */
50
+ className?: string;
51
+ /** Optional callback to resolve relative image URLs in markdown content. */
52
+ resolveImageUrl?: ResolveImageUrl;
53
+ }
54
+
55
+ /**
56
+ * A renderer component that can render file content.
57
+ */
58
+ export type FileRenderer = React.ComponentType<FileRendererProps>;
59
+
60
+ /**
61
+ * Registry mapping content types to renderer components.
62
+ * Users can override individual renderers by providing their own map.
63
+ */
64
+ export type RendererRegistry = Partial<Record<ContentType, FileRenderer>>;
65
+
66
+ // ============ STATE TYPES ============
67
+
68
+ /**
69
+ * Error information for the FileView.
70
+ */
71
+ export interface FileViewError {
72
+ /** Error message to display. */
73
+ message: string;
74
+ /** Optional retry callback. If provided, a retry button is shown. */
75
+ onRetry?: () => void;
76
+ }
77
+
78
+ // ============ COMPONENT PROPS ============
79
+
80
+ /**
81
+ * Props for the FileView component.
82
+ */
83
+ export interface FileViewProps {
84
+ // ============ CONTENT ============
85
+
86
+ /**
87
+ * Text content of the file.
88
+ * For text-based files (code, markdown, plaintext, csv), pass the string content.
89
+ * For binary files (images, PDFs), pass null and use `url` instead.
90
+ */
91
+ content: string | null;
92
+
93
+ /**
94
+ * URL for binary content.
95
+ * Used for images, PDFs, and other non-text content.
96
+ * Can be an object URL, data URL, or HTTP URL.
97
+ */
98
+ url?: string | null;
99
+
100
+ /**
101
+ * The file name including extension.
102
+ * Used for content type detection and display context.
103
+ * @example "README.md", "app.tsx", "photo.png"
104
+ */
105
+ fileName: string;
106
+
107
+ // ============ CONTENT TYPE ============
108
+
109
+ /**
110
+ * Explicit content type override.
111
+ * When provided, bypasses automatic detection from fileName.
112
+ */
113
+ contentType?: ContentType;
114
+
115
+ // ============ RENDERERS ============
116
+
117
+ /**
118
+ * Custom renderer overrides.
119
+ * Merges with (and overrides) the default renderer registry.
120
+ * Only the types you specify are overridden; others use defaults.
121
+ *
122
+ * @example
123
+ * renderers={{ code: MyCustomCodeRenderer }}
124
+ */
125
+ renderers?: RendererRegistry;
126
+
127
+ // ============ STATE ============
128
+
129
+ /** Loading state. When true, shows loading indicator. */
130
+ loading?: boolean;
131
+
132
+ /** Error state. When provided, shows error message with optional retry. */
133
+ error?: FileViewError | null;
134
+
135
+ // ============ CUSTOMIZATION ============
136
+
137
+ /** Custom loading component. Replaces the default spinner. */
138
+ loadingComponent?: React.ReactNode;
139
+
140
+ /** Custom empty state component. Shown when content is null/empty and not loading/error. */
141
+ emptyComponent?: React.ReactNode;
142
+
143
+ /**
144
+ * Empty state message. Used when no custom emptyComponent is provided.
145
+ * @default "No content to display"
146
+ */
147
+ emptyMessage?: string;
148
+
149
+ /** Custom error component. Replaces the default error display. */
150
+ errorComponent?: React.ComponentType<{ error: FileViewError }>;
151
+
152
+ // ============ STYLING ============
153
+
154
+ /** Additional class name for the outermost container. */
155
+ className?: string;
156
+
157
+ /** Additional class name passed to the active renderer. */
158
+ rendererClassName?: string;
159
+
160
+ // ============ IMAGE RESOLUTION ============
161
+
162
+ /**
163
+ * Optional callback to resolve image `src` values to renderable URLs.
164
+ * Used by MarkdownRenderer for relative/non-remote image paths.
165
+ * Remote URLs (http/https) and data URIs are passed through unchanged.
166
+ */
167
+ resolveImageUrl?: ResolveImageUrl;
168
+ }
169
+
170
+ // ============ DETECTION TYPES ============
171
+
172
+ /**
173
+ * Content type detection result.
174
+ */
175
+ export interface ContentTypeDetectionResult {
176
+ /** The detected content type. */
177
+ type: ContentType;
178
+ /** The file extension that was matched, if any. */
179
+ extension: string | null;
180
+ }
@@ -0,0 +1,157 @@
1
+ import type { ContentType, ContentTypeDetectionResult } from "../types";
2
+
3
+ /**
4
+ * Extension-to-content-type mapping.
5
+ */
6
+ const EXTENSION_MAP: Record<string, ContentType> = {
7
+ // Code / programming languages
8
+ ts: "code",
9
+ tsx: "code",
10
+ js: "code",
11
+ jsx: "code",
12
+ mjs: "code",
13
+ cjs: "code",
14
+ py: "code",
15
+ rb: "code",
16
+ go: "code",
17
+ rs: "code",
18
+ java: "code",
19
+ c: "code",
20
+ cpp: "code",
21
+ h: "code",
22
+ hpp: "code",
23
+ cs: "code",
24
+ php: "code",
25
+ swift: "code",
26
+ kt: "code",
27
+ scala: "code",
28
+ r: "code",
29
+ sql: "code",
30
+ sh: "code",
31
+ bash: "code",
32
+ zsh: "code",
33
+ ps1: "code",
34
+ bat: "code",
35
+ lua: "code",
36
+ perl: "code",
37
+ pl: "code",
38
+ // Config / data (code-rendered)
39
+ json: "code",
40
+ yaml: "code",
41
+ yml: "code",
42
+ toml: "code",
43
+ xml: "code",
44
+ html: "html",
45
+ htm: "html",
46
+ css: "code",
47
+ scss: "code",
48
+ sass: "code",
49
+ less: "code",
50
+ graphql: "code",
51
+ gql: "code",
52
+ // Optimization / domain-specific
53
+ lp: "code",
54
+ dat: "code",
55
+ // Markdown
56
+ md: "markdown",
57
+ mdx: "markdown",
58
+ // Images
59
+ png: "image",
60
+ jpg: "image",
61
+ jpeg: "image",
62
+ gif: "image",
63
+ svg: "image",
64
+ webp: "image",
65
+ ico: "image",
66
+ bmp: "image",
67
+ // PDF
68
+ pdf: "pdf",
69
+ // CSV
70
+ csv: "csv",
71
+ tsv: "csv",
72
+ // Plain text
73
+ txt: "plaintext",
74
+ log: "plaintext",
75
+ env: "plaintext",
76
+ };
77
+
78
+ /**
79
+ * Special file names (without extension) that map to content types.
80
+ */
81
+ const SPECIAL_FILES: Record<string, ContentType> = {
82
+ dockerfile: "code",
83
+ makefile: "code",
84
+ rakefile: "code",
85
+ gemfile: "code",
86
+ procfile: "code",
87
+ };
88
+
89
+ /**
90
+ * Extract the file extension from a file name.
91
+ * Handles dotfiles (e.g., ".gitignore" -> "gitignore") and
92
+ * compound extensions (e.g., "file.test.ts" -> "ts").
93
+ */
94
+ export function getFileExtension(fileName: string): string | null {
95
+ if (!fileName) return null;
96
+
97
+ const baseName = fileName.split("/").pop() || fileName;
98
+
99
+ // Dotfile with no other extension (e.g., ".gitignore")
100
+ if (baseName.startsWith(".") && !baseName.slice(1).includes(".")) {
101
+ return baseName.slice(1).toLowerCase();
102
+ }
103
+
104
+ const lastDot = baseName.lastIndexOf(".");
105
+ if (lastDot === -1 || lastDot === baseName.length - 1) return null;
106
+
107
+ return baseName.slice(lastDot + 1).toLowerCase();
108
+ }
109
+
110
+ /**
111
+ * Detect content type from a file name.
112
+ *
113
+ * @example
114
+ * detectContentType("app.tsx") // { type: "code", extension: "tsx" }
115
+ * detectContentType("README.md") // { type: "markdown", extension: "md" }
116
+ * detectContentType("photo.png") // { type: "image", extension: "png" }
117
+ * detectContentType("unknown.xyz") // { type: "unknown", extension: "xyz" }
118
+ */
119
+ export function detectContentType(
120
+ fileName: string,
121
+ ): ContentTypeDetectionResult {
122
+ // Check special file names first
123
+ const baseName = (fileName.split("/").pop() || fileName).toLowerCase();
124
+ const specialType = SPECIAL_FILES[baseName];
125
+ if (specialType) {
126
+ return { type: specialType, extension: baseName };
127
+ }
128
+
129
+ const extension = getFileExtension(fileName);
130
+ if (!extension) {
131
+ return { type: "unknown", extension: null };
132
+ }
133
+
134
+ const type = EXTENSION_MAP[extension] ?? "unknown";
135
+ return { type, extension };
136
+ }
137
+
138
+ /**
139
+ * Check if a content type is text-based (uses `content` string).
140
+ */
141
+ export function isTextContentType(contentType: ContentType): boolean {
142
+ return (
143
+ contentType === "code" ||
144
+ contentType === "markdown" ||
145
+ contentType === "plaintext" ||
146
+ contentType === "csv" ||
147
+ contentType === "html" ||
148
+ contentType === "unknown"
149
+ );
150
+ }
151
+
152
+ /**
153
+ * Check if a content type is URL-based (uses `url` string).
154
+ */
155
+ export function isUrlContentType(contentType: ContentType): boolean {
156
+ return contentType === "image" || contentType === "pdf";
157
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ detectContentType,
3
+ getFileExtension,
4
+ isTextContentType,
5
+ isUrlContentType,
6
+ } from "./contentTypeDetection";
7
+
8
+ export {
9
+ DEFAULT_RENDERERS,
10
+ mergeRenderers,
11
+ resolveRenderer,
12
+ } from "./rendererRegistry";
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Maps file extensions to shiki language IDs.
3
+ *
4
+ * Most extensions work directly as shiki lang IDs. This map only
5
+ * contains edge-cases where the extension differs from the shiki ID.
6
+ */
7
+ const EXTENSION_OVERRIDES: Record<string, string> = {
8
+ yml: "yaml",
9
+ htm: "html",
10
+ cjs: "javascript",
11
+ mjs: "javascript",
12
+ jsx: "jsx",
13
+ tsx: "tsx",
14
+ h: "c",
15
+ hpp: "cpp",
16
+ cs: "csharp",
17
+ rb: "ruby",
18
+ sh: "shellscript",
19
+ bash: "shellscript",
20
+ zsh: "shellscript",
21
+ ps1: "powershell",
22
+ bat: "bat",
23
+ kt: "kotlin",
24
+ rs: "rust",
25
+ gql: "graphql",
26
+ pl: "perl",
27
+ sass: "sass",
28
+ scss: "scss",
29
+ less: "less",
30
+ // Domain-specific / data formats without shiki support
31
+ lp: "text",
32
+ dat: "text",
33
+ env: "shellscript",
34
+ };
35
+
36
+ /**
37
+ * Special file names (without extension) mapped to shiki language IDs.
38
+ */
39
+ const SPECIAL_FILE_LANGUAGES: Record<string, string> = {
40
+ dockerfile: "dockerfile",
41
+ makefile: "makefile",
42
+ rakefile: "ruby",
43
+ gemfile: "ruby",
44
+ procfile: "yaml",
45
+ };
46
+
47
+ /**
48
+ * Get the shiki language ID for a given file name.
49
+ *
50
+ * @example
51
+ * getLanguageFromFileName("app.tsx") // "tsx"
52
+ * getLanguageFromFileName("config.yml") // "yaml"
53
+ * getLanguageFromFileName("Dockerfile") // "dockerfile"
54
+ * getLanguageFromFileName("unknown.xyz") // "text"
55
+ */
56
+ export function getLanguageFromFileName(fileName: string): string {
57
+ const baseName = (fileName.split("/").pop() || fileName).toLowerCase();
58
+
59
+ // Check special file names
60
+ const specialLang = SPECIAL_FILE_LANGUAGES[baseName];
61
+ if (specialLang) return specialLang;
62
+
63
+ // Extract extension
64
+ let ext: string | null = null;
65
+ if (baseName.startsWith(".") && !baseName.slice(1).includes(".")) {
66
+ ext = baseName.slice(1);
67
+ } else {
68
+ const lastDot = baseName.lastIndexOf(".");
69
+ if (lastDot !== -1 && lastDot !== baseName.length - 1) {
70
+ ext = baseName.slice(lastDot + 1);
71
+ }
72
+ }
73
+
74
+ if (!ext) return "text";
75
+
76
+ // Check overrides first, then use extension directly as shiki lang ID
77
+ return EXTENSION_OVERRIDES[ext] ?? ext;
78
+ }
@@ -0,0 +1,42 @@
1
+ import type { RendererRegistry, FileRenderer } from "../types";
2
+ import { CodeRenderer } from "../components/CodeRenderer";
3
+ import { MarkdownRenderer } from "../components/MarkdownRenderer";
4
+ import { ImageRenderer } from "../components/ImageRenderer";
5
+ import { PlainTextRenderer } from "../components/PlainTextRenderer";
6
+ import { CsvRenderer } from "../components/CsvRenderer";
7
+ import { HtmlRenderer } from "../components/HtmlRenderer";
8
+
9
+ /**
10
+ * Default renderer registry.
11
+ * Maps built-in content types to their default renderer components.
12
+ */
13
+ export const DEFAULT_RENDERERS: RendererRegistry = {
14
+ code: CodeRenderer,
15
+ markdown: MarkdownRenderer,
16
+ image: ImageRenderer,
17
+ plaintext: PlainTextRenderer,
18
+ csv: CsvRenderer,
19
+ html: HtmlRenderer,
20
+ };
21
+
22
+ /**
23
+ * Merge user-provided renderers with defaults.
24
+ * User renderers override defaults for the same content type.
25
+ */
26
+ export function mergeRenderers(
27
+ userRenderers?: RendererRegistry,
28
+ ): RendererRegistry {
29
+ if (!userRenderers) return DEFAULT_RENDERERS;
30
+ return { ...DEFAULT_RENDERERS, ...userRenderers };
31
+ }
32
+
33
+ /**
34
+ * Resolve a renderer for a given content type.
35
+ * Falls back to PlainTextRenderer if no match found.
36
+ */
37
+ export function resolveRenderer(
38
+ registry: RendererRegistry,
39
+ contentType: string,
40
+ ): FileRenderer {
41
+ return registry[contentType] ?? registry["plaintext"] ?? PlainTextRenderer;
42
+ }
@@ -26,7 +26,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
26
26
  <input
27
27
  type={type}
28
28
  className={cn(
29
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
29
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground hover:border-input-hover focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input md:text-sm",
30
30
  className
31
31
  )}
32
32
  ref={ref}
@@ -44,7 +44,7 @@ const PopoverContent = React.forwardRef<
44
44
  align={align}
45
45
  sideOffset={sideOffset}
46
46
  className={cn(
47
- "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none",
47
+ "z-50 w-auto max-w-[90vw] rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none",
48
48
  // Animation
49
49
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
50
50
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
@@ -20,7 +20,7 @@ const SelectTrigger = React.forwardRef<
20
20
  <SelectPrimitive.Trigger
21
21
  ref={ref}
22
22
  className={cn(
23
- "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground hover:border-input-hover focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input [&>span]:line-clamp-1",
24
24
  className
25
25
  )}
26
26
  {...props}
@@ -32,9 +32,11 @@ const Switch = React.forwardRef<
32
32
  // Focus styles
33
33
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
34
34
  // Disabled styles
35
- "disabled:cursor-not-allowed disabled:opacity-50",
35
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-muted",
36
36
  // Unchecked state
37
- "bg-input",
37
+ "bg-toggle-track",
38
+ // Hover
39
+ "hover:bg-toggle-track/80 data-[state=checked]:hover:bg-primary/80",
38
40
  // Checked state
39
41
  "data-[state=checked]:bg-primary",
40
42
  className
@@ -46,7 +48,7 @@ const Switch = React.forwardRef<
46
48
  className={cn(
47
49
  // Base styles
48
50
  "pointer-events-none block h-4 w-4 rounded-full",
49
- "bg-background shadow-lg ring-0",
51
+ "bg-toggle-track-foreground shadow-lg ring-0",
50
52
  "transition-transform",
51
53
  // Position based on state
52
54
  "data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
@@ -22,7 +22,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
22
22
  return (
23
23
  <textarea
24
24
  className={cn(
25
- "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
25
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground hover:border-input-hover focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input md:text-sm",
26
26
  className
27
27
  )}
28
28
  ref={ref}
package/src/index.ts CHANGED
@@ -321,6 +321,57 @@ export {
321
321
  type UseContextMenuResult,
322
322
  } from "./components/context-menu";
323
323
 
324
+ // FileView - Configurable file viewer with pluggable renderers
325
+ export {
326
+ // Main component
327
+ FileView,
328
+
329
+ // Types
330
+ type BuiltInContentType,
331
+ type ContentType,
332
+ type ContentTypeDetectionResult,
333
+ type FileRendererProps,
334
+ type FileRenderer,
335
+ type RendererRegistry,
336
+ type FileViewError,
337
+ type FileViewProps,
338
+ type ResolveImageUrl,
339
+
340
+ // Sub-components (for custom composition or standalone use)
341
+ CodeRenderer,
342
+ MarkdownRenderer,
343
+ ImageRenderer,
344
+ PlainTextRenderer,
345
+ CsvRenderer,
346
+ HtmlRenderer,
347
+
348
+ // Hooks
349
+ useContentType,
350
+ type UseContentTypeOptions,
351
+ type UseContentTypeReturn,
352
+
353
+ // Utilities
354
+ detectContentType,
355
+ getFileExtension,
356
+ isTextContentType,
357
+ isUrlContentType,
358
+ DEFAULT_RENDERERS,
359
+ mergeRenderers,
360
+ resolveRenderer,
361
+ } from "./components/file-view/index";
362
+
363
+ // Branding
364
+ export {
365
+ OptilogicLogo,
366
+ type OptilogicLogoProps,
367
+ OptilogicLogoWithText,
368
+ type OptilogicLogoWithTextProps,
369
+ CosmicFrogIcon,
370
+ type CosmicFrogIconProps,
371
+ DataStarIcon,
372
+ type DataStarIconProps,
373
+ } from "./components/branding";
374
+
324
375
  // Theme system
325
376
  export {
326
377
  // Types
package/src/styles.css CHANGED
@@ -40,15 +40,26 @@
40
40
  --border: 214.3 31.8% 91.4%;
41
41
  --input: 214.3 31.8% 91.4%;
42
42
  --ring: 222.2 84% 4.9%;
43
+ --toggle-track: var(--muted);
44
+ --toggle-track-foreground: var(--background);
45
+ --input-hover: var(--foreground);
43
46
  --divider: 214.3 31.8% 91.4%;
44
47
  --chip: 210 40% 96.1%;
45
48
  --chip-foreground: 222.2 47.4% 11.2%;
49
+ --disabled-opacity: 0.5;
46
50
  --radius: 0.5rem;
47
51
  --chart-1: 12 76% 61%;
48
52
  --chart-2: 173 58% 39%;
49
53
  --chart-3: 197 37% 24%;
50
54
  --chart-4: 43 74% 66%;
51
55
  --chart-5: 27 87% 67%;
56
+ --chart-6: 262 60% 55%;
57
+ --chart-7: 142 60% 45%;
58
+ --chart-8: 350 65% 55%;
59
+ --chart-9: 200 70% 50%;
60
+ --chart-10: 60 65% 50%;
61
+ --chart-11: 310 55% 50%;
62
+ --chart-12: 180 50% 45%;
52
63
  }
53
64
 
54
65
  .dark {
@@ -75,14 +86,25 @@
75
86
  --border: 217.2 32.6% 17.5%;
76
87
  --input: 217.2 32.6% 17.5%;
77
88
  --ring: 212.7 26.8% 83.9%;
89
+ --toggle-track: var(--muted);
90
+ --toggle-track-foreground: var(--background);
91
+ --input-hover: var(--foreground);
78
92
  --divider: 217.2 32.6% 17.5%;
79
93
  --chip: 217.2 32.6% 17.5%;
80
94
  --chip-foreground: 210 40% 98%;
95
+ --disabled-opacity: 0.5;
81
96
  --chart-1: 220 70% 50%;
82
97
  --chart-2: 160 60% 45%;
83
98
  --chart-3: 30 80% 55%;
84
99
  --chart-4: 280 65% 60%;
85
100
  --chart-5: 340 75% 55%;
101
+ --chart-6: 120 55% 50%;
102
+ --chart-7: 200 75% 55%;
103
+ --chart-8: 50 80% 55%;
104
+ --chart-9: 0 70% 55%;
105
+ --chart-10: 260 65% 60%;
106
+ --chart-11: 180 60% 50%;
107
+ --chart-12: 90 55% 55%;
86
108
  }
87
109
  }
88
110
 
@@ -83,10 +83,19 @@ export const optiUiPreset: Partial<Config> = {
83
83
 
84
84
  // Border, input, ring
85
85
  border: "hsl(var(--border))",
86
- input: "hsl(var(--input))",
86
+ input: {
87
+ DEFAULT: "hsl(var(--input))",
88
+ hover: "hsl(var(--input-hover))",
89
+ },
87
90
  ring: "hsl(var(--ring))",
88
91
  divider: "hsl(var(--divider))",
89
92
 
93
+ // Toggle/switch track
94
+ "toggle-track": {
95
+ DEFAULT: "hsl(var(--toggle-track))",
96
+ foreground: "hsl(var(--toggle-track-foreground))",
97
+ },
98
+
90
99
  // Chip
91
100
  chip: {
92
101
  DEFAULT: "hsl(var(--chip))",
@@ -100,6 +109,13 @@ export const optiUiPreset: Partial<Config> = {
100
109
  3: "hsl(var(--chart-3))",
101
110
  4: "hsl(var(--chart-4))",
102
111
  5: "hsl(var(--chart-5))",
112
+ 6: "hsl(var(--chart-6))",
113
+ 7: "hsl(var(--chart-7))",
114
+ 8: "hsl(var(--chart-8))",
115
+ 9: "hsl(var(--chart-9))",
116
+ 10: "hsl(var(--chart-10))",
117
+ 11: "hsl(var(--chart-11))",
118
+ 12: "hsl(var(--chart-12))",
103
119
  },
104
120
  },
105
121
  borderRadius: {
@@ -38,4 +38,9 @@ export {
38
38
  areThemesEqual,
39
39
  exportTheme,
40
40
  importTheme,
41
+ getRelativeLuminance,
42
+ getContrastRatio,
43
+ validateThemeContrast,
41
44
  } from "./utils";
45
+
46
+ export type { ContrastWarning } from "./utils";