@nordmanai/nif 0.0.0-stage → 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/README.md +79 -2
- package/bin/nif.mjs +196 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +12961 -0
- package/dist/styles.css +2 -0
- package/dist/tokens.css +72 -0
- package/dist/ui/components/nm-ai/nm-ai.d.ts +222 -0
- package/dist/ui/components/nm-avatar/nm-avatar.d.ts +12 -0
- package/dist/ui/components/nm-bar-overflow/nm-bar-overflow.d.ts +15 -0
- package/dist/ui/components/nm-bar-overflow/use-bar-overflow.d.ts +7 -0
- package/dist/ui/components/nm-brand-logo/nm-brand-logo.d.ts +10 -0
- package/dist/ui/components/nm-breadcrumb/nm-breadcrumb.d.ts +11 -0
- package/dist/ui/components/nm-button/nm-button.d.ts +19 -0
- package/dist/ui/components/nm-button-group/nm-button-group.d.ts +17 -0
- package/dist/ui/components/nm-calendar/nm-calendar.d.ts +3 -0
- package/dist/ui/components/nm-card/nm-card.d.ts +34 -0
- package/dist/ui/components/nm-chatbox/nm-chatbox-icon.d.ts +7 -0
- package/dist/ui/components/nm-chatbox/nm-chatbox.d.ts +19 -0
- package/dist/ui/components/nm-checkbox/nm-checkbox.d.ts +2 -0
- package/dist/ui/components/nm-chip/nm-chip.d.ts +38 -0
- package/dist/ui/components/nm-code-block/nm-code-block.d.ts +55 -0
- package/dist/ui/components/nm-collection/nm-collection-bar.d.ts +16 -0
- package/dist/ui/components/nm-collection/nm-collection-grid.d.ts +9 -0
- package/dist/ui/components/nm-collection/nm-collection-query.d.ts +18 -0
- package/dist/ui/components/nm-collection/nm-collection-row-menu.d.ts +7 -0
- package/dist/ui/components/nm-collection/nm-collection-table.d.ts +13 -0
- package/dist/ui/components/nm-collection/nm-collection-tile-actions.d.ts +7 -0
- package/dist/ui/components/nm-collection/nm-collection-title.d.ts +10 -0
- package/dist/ui/components/nm-collection/use-collection-scroll-reveal.d.ts +3 -0
- package/dist/ui/components/nm-color-picker/nm-color-picker.d.ts +15 -0
- package/dist/ui/components/nm-combobox/nm-combobox.d.ts +26 -0
- package/dist/ui/components/nm-command/nm-command.d.ts +37 -0
- package/dist/ui/components/nm-composer/nm-composer.d.ts +33 -0
- package/dist/ui/components/nm-context-menu/nm-context-menu.d.ts +9 -0
- package/dist/ui/components/nm-date-picker/nm-date-picker.d.ts +35 -0
- package/dist/ui/components/nm-dialog/nm-dialog.d.ts +13 -0
- package/dist/ui/components/nm-dropdown/nm-dropdown-item.d.ts +26 -0
- package/dist/ui/components/nm-dropdown/nm-dropdown-options.d.ts +12 -0
- package/dist/ui/components/nm-dropdown/nm-dropdown-panel.d.ts +16 -0
- package/dist/ui/components/nm-dropdown/nm-dropdown-placement.d.ts +55 -0
- package/dist/ui/components/nm-dropdown/nm-dropdown.d.ts +31 -0
- package/dist/ui/components/nm-dropdown-menu/nm-dropdown-menu.d.ts +30 -0
- package/dist/ui/components/nm-field/nm-field.d.ts +52 -0
- package/dist/ui/components/nm-form-field/nm-form-field.d.ts +18 -0
- package/dist/ui/components/nm-gallery/nm-gallery.d.ts +8 -0
- package/dist/ui/components/nm-hover-card/nm-hover-card.d.ts +5 -0
- package/dist/ui/components/nm-icon/nm-icon.d.ts +7 -0
- package/dist/ui/components/nm-icon/nm-lucide.d.ts +7 -0
- package/dist/ui/components/nm-icon-border/nm-icon-border.d.ts +21 -0
- package/dist/ui/components/nm-input/nm-input.d.ts +5 -0
- package/dist/ui/components/nm-input-group/nm-input-group.d.ts +9 -0
- package/dist/ui/components/nm-label/nm-label.d.ts +2 -0
- package/dist/ui/components/nm-list-item/nm-list-item.d.ts +23 -0
- package/dist/ui/components/nm-menubar/nm-menubar.d.ts +9 -0
- package/dist/ui/components/nm-message/nm-message-actions.d.ts +7 -0
- package/dist/ui/components/nm-message/nm-message-meta.d.ts +10 -0
- package/dist/ui/components/nm-message/nm-text-shimmer.d.ts +4 -0
- package/dist/ui/components/nm-message/nm-trace.d.ts +58 -0
- package/dist/ui/components/nm-modal/nm-modal.d.ts +35 -0
- package/dist/ui/components/nm-native-select/nm-native-select.d.ts +5 -0
- package/dist/ui/components/nm-panel/nm-panel.d.ts +14 -0
- package/dist/ui/components/nm-panel-bar/nm-panel-bar.d.ts +17 -0
- package/dist/ui/components/nm-popover/nm-popover.d.ts +8 -0
- package/dist/ui/components/nm-progress/nm-progress.d.ts +3 -0
- package/dist/ui/components/nm-prose/nm-prose.d.ts +6 -0
- package/dist/ui/components/nm-questionnaire/nm-questionnaire.d.ts +17 -0
- package/dist/ui/components/nm-radio-group/nm-radio-group.d.ts +4 -0
- package/dist/ui/components/nm-rail-icon/nm-rail-icon.d.ts +6 -0
- package/dist/ui/components/nm-resize-handle/nm-resize-handle.d.ts +18 -0
- package/dist/ui/components/nm-segmented/nm-segmented.d.ts +20 -0
- package/dist/ui/components/nm-select/nm-select.d.ts +22 -0
- package/dist/ui/components/nm-separator/nm-separator.d.ts +2 -0
- package/dist/ui/components/nm-side-panel/nm-side-panel.d.ts +33 -0
- package/dist/ui/components/nm-slider/nm-slider.d.ts +6 -0
- package/dist/ui/components/nm-small-icon-button/nm-small-icon-button.d.ts +21 -0
- package/dist/ui/components/nm-status-bar/nm-status-bar.d.ts +8 -0
- package/dist/ui/components/nm-switch/nm-switch.d.ts +2 -0
- package/dist/ui/components/nm-table/nm-table.d.ts +38 -0
- package/dist/ui/components/nm-tabs/nm-tabs.d.ts +9 -0
- package/dist/ui/components/nm-textarea/nm-textarea.d.ts +2 -0
- package/dist/ui/components/nm-toggle/nm-toggle.d.ts +9 -0
- package/dist/ui/components/nm-toolbar/nm-toolbar.d.ts +21 -0
- package/dist/ui/components/nm-tooltip/nm-tooltip.d.ts +5 -0
- package/dist/ui/components/nm-top-bar/nm-top-bar.d.ts +12 -0
- package/dist/ui/components/nordman-icon-button/nordman-icon-button.d.ts +29 -0
- package/dist/ui/components/nordman-login/nordman-login.d.ts +36 -0
- package/dist/ui/components/nordman-login-field/nordman-login-field.d.ts +13 -0
- package/dist/ui/components/nordman-login-forgot/nordman-login-forgot.d.ts +9 -0
- package/dist/ui/components/nordman-login-google/nordman-login-google.d.ts +6 -0
- package/dist/ui/components/nordman-login-hero/nordman-login-hero.d.ts +6 -0
- package/dist/ui/components/nordman-login-register/nordman-login-register.d.ts +11 -0
- package/dist/ui/components/nordman-login-reset/nordman-login-reset.d.ts +9 -0
- package/dist/ui/components/nordman-login-reveal/nordman-login-reveal.d.ts +6 -0
- package/dist/ui/components/nordman-login-sign-in/nordman-login-sign-in.d.ts +11 -0
- package/dist/ui/components/nordman-toggle/nordman-toggle.d.ts +12 -0
- package/dist/ui/config/nm-ai.d.ts +44 -0
- package/dist/ui/config/nm-app-panel.d.ts +36 -0
- package/dist/ui/config/nm-appearance.d.ts +100 -0
- package/dist/ui/config/nm-bar-overflow.d.ts +9 -0
- package/dist/ui/config/nm-button.d.ts +31 -0
- package/dist/ui/config/nm-chatbox-icons.d.ts +42 -0
- package/dist/ui/config/nm-chatbox.d.ts +40 -0
- package/dist/ui/config/nm-collection.d.ts +88 -0
- package/dist/ui/config/nm-composer.d.ts +42 -0
- package/dist/ui/config/nm-dropdown.d.ts +56 -0
- package/dist/ui/config/nm-fade.d.ts +6 -0
- package/dist/ui/config/nm-field.d.ts +38 -0
- package/dist/ui/config/nm-header-icons.d.ts +15 -0
- package/dist/ui/config/nm-icon-border.d.ts +3 -0
- package/dist/ui/config/nm-icon-button.d.ts +26 -0
- package/dist/ui/config/nm-icons.d.ts +371 -0
- package/dist/ui/config/nm-list-item.d.ts +14 -0
- package/dist/ui/config/nm-message.d.ts +87 -0
- package/dist/ui/config/nm-modal.d.ts +67 -0
- package/dist/ui/config/nm-panel-bar.d.ts +9 -0
- package/dist/ui/config/nm-questionnaire.d.ts +57 -0
- package/dist/ui/config/nm-radius.d.ts +49 -0
- package/dist/ui/config/nm-rail-icons.d.ts +14 -0
- package/dist/ui/config/nm-rail.d.ts +5 -0
- package/dist/ui/config/nm-segmented.d.ts +17 -0
- package/dist/ui/config/nm-side-panel.d.ts +33 -0
- package/dist/ui/config/nm-site-palette.d.ts +30 -0
- package/dist/ui/config/nm-small-icon-button.d.ts +12 -0
- package/dist/ui/config/nm-tabs.d.ts +8 -0
- package/dist/ui/config/nm-top-bar.d.ts +4 -0
- package/dist/ui/config/nordman-chrome.d.ts +12 -0
- package/dist/ui/config/nordman-login.d.ts +23 -0
- package/dist/ui/content/nm-modal.d.ts +1 -0
- package/dist/ui/content/nordman-login.d.ts +38 -0
- package/dist/ui/kit-logger.d.ts +1 -0
- package/dist/ui/lib/appearance/appearanceState.d.ts +28 -0
- package/dist/ui/lib/appearance/applyAppearance.d.ts +5 -0
- package/dist/ui/lib/appearance/cloudOrbAppearance.d.ts +4 -0
- package/dist/ui/lib/appearance/color.d.ts +13 -0
- package/dist/ui/lib/appearance/deriveTokens.d.ts +6 -0
- package/dist/ui/lib/appearance/index.d.ts +6 -0
- package/dist/ui/lib/appearance/markTokens.d.ts +2 -0
- package/dist/ui/lib/appearance/readLayoutMetrics.d.ts +4 -0
- package/dist/ui/lib/bar-overflow.d.ts +13 -0
- package/dist/ui/lib/logger.d.ts +8 -0
- package/dist/ui/lib/menu-motion.d.ts +72 -0
- package/dist/ui/lib/nm-class.d.ts +2 -0
- package/dist/ui/lib/nordman-ui-locale.d.ts +9 -0
- package/interface/app-panel/code.svg +5 -0
- package/interface/app-panel/console.svg +5 -0
- package/interface/app-panel/copy.svg +5 -0
- package/interface/app-panel/ellipsis.svg +7 -0
- package/interface/app-panel/message.svg +5 -0
- package/interface/app-panel/network.svg +10 -0
- package/interface/app-panel/play.svg +5 -0
- package/interface/app-panel/plus.svg +5 -0
- package/interface/chatbox/app.svg +5 -0
- package/interface/chatbox/audio-lines.svg +5 -0
- package/interface/chatbox/check.svg +5 -0
- package/interface/chatbox/globe.svg +5 -0
- package/interface/chatbox/integration.svg +5 -0
- package/interface/chatbox/mic.svg +5 -0
- package/interface/chatbox/paperclip.svg +5 -0
- package/interface/chatbox/pointer.svg +5 -0
- package/interface/chatbox/send.svg +5 -0
- package/interface/chatbox/settings.svg +5 -0
- package/interface/chatbox/spreadsheet.svg +5 -0
- package/interface/chatbox/x.svg +5 -0
- package/interface/collection/audio.svg +5 -0
- package/interface/collection/download.svg +5 -0
- package/interface/collection/expand.svg +5 -0
- package/interface/collection/grid.svg +10 -0
- package/interface/collection/image.svg +5 -0
- package/interface/collection/spreadsheet.svg +5 -0
- package/interface/collection/video.svg +5 -0
- package/interface/header/app.svg +5 -0
- package/interface/header/bot.svg +5 -0
- package/interface/header/chat.svg +10 -0
- package/interface/header/folder.svg +5 -0
- package/interface/header/layers.svg +10 -0
- package/interface/header/panel.svg +5 -0
- package/interface/header/play.svg +3 -0
- package/interface/header/schedule.svg +8 -0
- package/interface/header/search.svg +5 -0
- package/interface/header/settings.svg +5 -0
- package/interface/header/users.svg +5 -0
- package/interface/message/chevron.svg +5 -0
- package/interface/message/copy.svg +5 -0
- package/interface/message/fork.svg +5 -0
- package/interface/message/refresh.svg +5 -0
- package/interface/message/volume.svg +5 -0
- package/interface/rail/app.svg +5 -0
- package/interface/rail/bot.svg +5 -0
- package/interface/rail/chats.svg +5 -0
- package/interface/rail/folder.svg +5 -0
- package/interface/rail/library.svg +5 -0
- package/interface/rail/plus-sm.svg +5 -0
- package/interface/rail/plus.svg +5 -0
- package/interface/rail/search.svg +5 -0
- package/interface/rail/settings.svg +8 -0
- package/interface/rail/spreadsheet.svg +5 -0
- package/package.json +79 -4
- package/registry/index.json +1634 -0
- package/registry/tokens.css +72 -0
- package/registry/ui/components/nm-ai/nm-ai.css +358 -0
- package/registry/ui/components/nm-ai/nm-ai.tsx +632 -0
- package/registry/ui/components/nm-app-panel/nm-app-panel.css +169 -0
- package/registry/ui/components/nm-ask/nm-ask.css +277 -0
- package/registry/ui/components/nm-avatar/nm-avatar.css +85 -0
- package/registry/ui/components/nm-avatar/nm-avatar.tsx +30 -0
- package/registry/ui/components/nm-bar-overflow/nm-bar-overflow.css +43 -0
- package/registry/ui/components/nm-bar-overflow/nm-bar-overflow.tsx +61 -0
- package/registry/ui/components/nm-bar-overflow/use-bar-overflow.ts +92 -0
- package/registry/ui/components/nm-brand-logo/nm-brand-logo.css +20 -0
- package/registry/ui/components/nm-brand-logo/nm-brand-logo.tsx +21 -0
- package/registry/ui/components/nm-breadcrumb/nm-breadcrumb.css +57 -0
- package/registry/ui/components/nm-breadcrumb/nm-breadcrumb.tsx +42 -0
- package/registry/ui/components/nm-button/nm-button.css +110 -0
- package/registry/ui/components/nm-button/nm-button.tsx +58 -0
- package/registry/ui/components/nm-button-group/nm-button-group.css +87 -0
- package/registry/ui/components/nm-button-group/nm-button-group.tsx +32 -0
- package/registry/ui/components/nm-calendar/nm-calendar.css +178 -0
- package/registry/ui/components/nm-calendar/nm-calendar.tsx +43 -0
- package/registry/ui/components/nm-card/nm-card.css +130 -0
- package/registry/ui/components/nm-card/nm-card.tsx +83 -0
- package/registry/ui/components/nm-chatbox/nm-chatbox-icon.tsx +39 -0
- package/registry/ui/components/nm-chatbox/nm-chatbox.css +682 -0
- package/registry/ui/components/nm-chatbox/nm-chatbox.tsx +101 -0
- package/registry/ui/components/nm-checkbox/nm-checkbox.css +61 -0
- package/registry/ui/components/nm-checkbox/nm-checkbox.tsx +14 -0
- package/registry/ui/components/nm-chip/nm-chip.css +112 -0
- package/registry/ui/components/nm-chip/nm-chip.tsx +86 -0
- package/registry/ui/components/nm-code-block/nm-code-block.css +240 -0
- package/registry/ui/components/nm-code-block/nm-code-block.tsx +196 -0
- package/registry/ui/components/nm-collection/nm-collection-bar.tsx +65 -0
- package/registry/ui/components/nm-collection/nm-collection-grid.tsx +69 -0
- package/registry/ui/components/nm-collection/nm-collection-query.tsx +99 -0
- package/registry/ui/components/nm-collection/nm-collection-row-menu.tsx +55 -0
- package/registry/ui/components/nm-collection/nm-collection-table.tsx +99 -0
- package/registry/ui/components/nm-collection/nm-collection-tile-actions.tsx +50 -0
- package/registry/ui/components/nm-collection/nm-collection-title.tsx +25 -0
- package/registry/ui/components/nm-collection/nm-collection.css +353 -0
- package/registry/ui/components/nm-collection/use-collection-scroll-reveal.ts +38 -0
- package/registry/ui/components/nm-color-picker/nm-color-picker.css +67 -0
- package/registry/ui/components/nm-color-picker/nm-color-picker.tsx +79 -0
- package/registry/ui/components/nm-combobox/nm-combobox.css +72 -0
- package/registry/ui/components/nm-combobox/nm-combobox.tsx +80 -0
- package/registry/ui/components/nm-command/nm-command.css +190 -0
- package/registry/ui/components/nm-command/nm-command.tsx +125 -0
- package/registry/ui/components/nm-composer/nm-composer.css +178 -0
- package/registry/ui/components/nm-composer/nm-composer.tsx +151 -0
- package/registry/ui/components/nm-context-menu/nm-context-menu.tsx +25 -0
- package/registry/ui/components/nm-date-picker/nm-date-picker.css +140 -0
- package/registry/ui/components/nm-date-picker/nm-date-picker.tsx +153 -0
- package/registry/ui/components/nm-dialog/nm-dialog.css +84 -0
- package/registry/ui/components/nm-dialog/nm-dialog.tsx +40 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown-item.ts +68 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown-options.tsx +242 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown-panel.tsx +171 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown-placement.ts +157 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown.css +312 -0
- package/registry/ui/components/nm-dropdown/nm-dropdown.tsx +412 -0
- package/registry/ui/components/nm-dropdown-menu/nm-dropdown-menu.css +145 -0
- package/registry/ui/components/nm-dropdown-menu/nm-dropdown-menu.tsx +118 -0
- package/registry/ui/components/nm-field/nm-field.css +356 -0
- package/registry/ui/components/nm-field/nm-field.tsx +355 -0
- package/registry/ui/components/nm-form-field/nm-form-field.css +103 -0
- package/registry/ui/components/nm-form-field/nm-form-field.tsx +71 -0
- package/registry/ui/components/nm-gallery/nm-gallery.css +66 -0
- package/registry/ui/components/nm-gallery/nm-gallery.tsx +31 -0
- package/registry/ui/components/nm-hover-card/nm-hover-card.css +26 -0
- package/registry/ui/components/nm-hover-card/nm-hover-card.tsx +28 -0
- package/registry/ui/components/nm-icon/nm-icon.css +6 -0
- package/registry/ui/components/nm-icon/nm-icon.tsx +69 -0
- package/registry/ui/components/nm-icon/nm-lucide.tsx +180 -0
- package/registry/ui/components/nm-icon-border/nm-icon-border.tsx +56 -0
- package/registry/ui/components/nm-input/nm-input.css +75 -0
- package/registry/ui/components/nm-input/nm-input.tsx +7 -0
- package/registry/ui/components/nm-input-group/nm-input-group.css +66 -0
- package/registry/ui/components/nm-input-group/nm-input-group.tsx +20 -0
- package/registry/ui/components/nm-label/nm-label.css +11 -0
- package/registry/ui/components/nm-label/nm-label.tsx +6 -0
- package/registry/ui/components/nm-list-item/nm-list-item.css +177 -0
- package/registry/ui/components/nm-list-item/nm-list-item.tsx +112 -0
- package/registry/ui/components/nm-menubar/nm-menubar.css +32 -0
- package/registry/ui/components/nm-menubar/nm-menubar.tsx +19 -0
- package/registry/ui/components/nm-message/nm-message-actions.tsx +24 -0
- package/registry/ui/components/nm-message/nm-message-meta.tsx +49 -0
- package/registry/ui/components/nm-message/nm-message.css +686 -0
- package/registry/ui/components/nm-message/nm-text-shimmer.tsx +6 -0
- package/registry/ui/components/nm-message/nm-trace.tsx +219 -0
- package/registry/ui/components/nm-modal/nm-modal.css +763 -0
- package/registry/ui/components/nm-modal/nm-modal.tsx +497 -0
- package/registry/ui/components/nm-native-select/nm-native-select.css +47 -0
- package/registry/ui/components/nm-native-select/nm-native-select.tsx +23 -0
- package/registry/ui/components/nm-panel/nm-panel.css +73 -0
- package/registry/ui/components/nm-panel/nm-panel.tsx +35 -0
- package/registry/ui/components/nm-panel-bar/nm-panel-bar.css +20 -0
- package/registry/ui/components/nm-panel-bar/nm-panel-bar.tsx +48 -0
- package/registry/ui/components/nm-popover/nm-popover.css +43 -0
- package/registry/ui/components/nm-popover/nm-popover.tsx +40 -0
- package/registry/ui/components/nm-progress/nm-progress.css +21 -0
- package/registry/ui/components/nm-progress/nm-progress.tsx +14 -0
- package/registry/ui/components/nm-prose/nm-prose.css +81 -0
- package/registry/ui/components/nm-prose/nm-prose.tsx +10 -0
- package/registry/ui/components/nm-questionnaire/nm-questionnaire.css +182 -0
- package/registry/ui/components/nm-questionnaire/nm-questionnaire.tsx +221 -0
- package/registry/ui/components/nm-radio-group/nm-radio-group.css +48 -0
- package/registry/ui/components/nm-radio-group/nm-radio-group.tsx +15 -0
- package/registry/ui/components/nm-rail-icon/nm-rail-icon.css +6 -0
- package/registry/ui/components/nm-rail-icon/nm-rail-icon.tsx +28 -0
- package/registry/ui/components/nm-resize-handle/nm-resize-handle.css +45 -0
- package/registry/ui/components/nm-resize-handle/nm-resize-handle.tsx +102 -0
- package/registry/ui/components/nm-segmented/nm-segmented.css +104 -0
- package/registry/ui/components/nm-segmented/nm-segmented.tsx +77 -0
- package/registry/ui/components/nm-select/nm-select.css +78 -0
- package/registry/ui/components/nm-select/nm-select.tsx +64 -0
- package/registry/ui/components/nm-separator/nm-separator.css +14 -0
- package/registry/ui/components/nm-separator/nm-separator.tsx +6 -0
- package/registry/ui/components/nm-side-panel/nm-side-panel.css +93 -0
- package/registry/ui/components/nm-side-panel/nm-side-panel.tsx +86 -0
- package/registry/ui/components/nm-slider/nm-slider.css +53 -0
- package/registry/ui/components/nm-slider/nm-slider.tsx +40 -0
- package/registry/ui/components/nm-small-icon-button/nm-small-icon-button.tsx +66 -0
- package/registry/ui/components/nm-status-bar/nm-status-bar.css +50 -0
- package/registry/ui/components/nm-status-bar/nm-status-bar.tsx +28 -0
- package/registry/ui/components/nm-switch/nm-switch.css +45 -0
- package/registry/ui/components/nm-switch/nm-switch.tsx +10 -0
- package/registry/ui/components/nm-table/nm-table.css +155 -0
- package/registry/ui/components/nm-table/nm-table.tsx +95 -0
- package/registry/ui/components/nm-tabs/nm-tabs.css +28 -0
- package/registry/ui/components/nm-tabs/nm-tabs.tsx +32 -0
- package/registry/ui/components/nm-textarea/nm-textarea.css +41 -0
- package/registry/ui/components/nm-textarea/nm-textarea.tsx +6 -0
- package/registry/ui/components/nm-toggle/nm-toggle.css +55 -0
- package/registry/ui/components/nm-toggle/nm-toggle.tsx +16 -0
- package/registry/ui/components/nm-toolbar/nm-toolbar.css +42 -0
- package/registry/ui/components/nm-toolbar/nm-toolbar.tsx +54 -0
- package/registry/ui/components/nm-tooltip/nm-tooltip.css +37 -0
- package/registry/ui/components/nm-tooltip/nm-tooltip.tsx +35 -0
- package/registry/ui/components/nm-top-bar/nm-top-bar.css +29 -0
- package/registry/ui/components/nm-top-bar/nm-top-bar.tsx +34 -0
- package/registry/ui/components/nordman-icon-button/nordman-icon-button.css +119 -0
- package/registry/ui/components/nordman-icon-button/nordman-icon-button.tsx +185 -0
- package/registry/ui/components/nordman-login/nordman-login.css +195 -0
- package/registry/ui/components/nordman-login/nordman-login.tsx +136 -0
- package/registry/ui/components/nordman-login-field/nordman-login-field.tsx +55 -0
- package/registry/ui/components/nordman-login-forgot/nordman-login-forgot.tsx +56 -0
- package/registry/ui/components/nordman-login-google/nordman-login-google.tsx +28 -0
- package/registry/ui/components/nordman-login-hero/nordman-login-hero.tsx +38 -0
- package/registry/ui/components/nordman-login-register/nordman-login-register.tsx +70 -0
- package/registry/ui/components/nordman-login-reset/nordman-login-reset.tsx +71 -0
- package/registry/ui/components/nordman-login-reveal/nordman-login-reveal.tsx +41 -0
- package/registry/ui/components/nordman-login-sign-in/nordman-login-sign-in.tsx +69 -0
- package/registry/ui/components/nordman-toggle/nordman-toggle.css +28 -0
- package/registry/ui/components/nordman-toggle/nordman-toggle.tsx +35 -0
- package/registry/ui/config/nm-ai.ts +59 -0
- package/registry/ui/config/nm-appearance.ts +275 -0
- package/registry/ui/config/nm-bar-overflow.ts +10 -0
- package/registry/ui/config/nm-button.ts +38 -0
- package/registry/ui/config/nm-chatbox-icons.ts +67 -0
- package/registry/ui/config/nm-chatbox.ts +42 -0
- package/registry/ui/config/nm-collection.ts +101 -0
- package/registry/ui/config/nm-composer.ts +39 -0
- package/registry/ui/config/nm-dropdown.ts +62 -0
- package/registry/ui/config/nm-field.ts +43 -0
- package/registry/ui/config/nm-icon-button.ts +35 -0
- package/registry/ui/config/nm-icons.ts +417 -0
- package/registry/ui/config/nm-list-item.ts +14 -0
- package/registry/ui/config/nm-message.ts +106 -0
- package/registry/ui/config/nm-modal.ts +76 -0
- package/registry/ui/config/nm-panel-bar.ts +11 -0
- package/registry/ui/config/nm-questionnaire.ts +53 -0
- package/registry/ui/config/nm-radius.ts +56 -0
- package/registry/ui/config/nm-rail-icons.ts +27 -0
- package/registry/ui/config/nm-segmented.ts +19 -0
- package/registry/ui/config/nm-side-panel.ts +16 -0
- package/registry/ui/config/nm-site-palette.ts +30 -0
- package/registry/ui/config/nm-small-icon-button.ts +17 -0
- package/registry/ui/config/nm-tabs.ts +9 -0
- package/registry/ui/config/nm-top-bar.ts +4 -0
- package/registry/ui/config/nordman-chrome.ts +14 -0
- package/registry/ui/config/nordman-login.ts +28 -0
- package/registry/ui/content/nm-modal.ts +5 -0
- package/registry/ui/content/nordman-login.ts +85 -0
- package/registry/ui/kit-logger.ts +1 -0
- package/registry/ui/lib/appearance/appearanceState.ts +240 -0
- package/registry/ui/lib/appearance/applyAppearance.ts +36 -0
- package/registry/ui/lib/appearance/cloudOrbAppearance.ts +36 -0
- package/registry/ui/lib/appearance/color.ts +96 -0
- package/registry/ui/lib/appearance/deriveTokens.ts +218 -0
- package/registry/ui/lib/appearance/index.ts +6 -0
- package/registry/ui/lib/appearance/markTokens.ts +49 -0
- package/registry/ui/lib/appearance/readLayoutMetrics.ts +28 -0
- package/registry/ui/lib/bar-overflow.ts +41 -0
- package/registry/ui/lib/logger.ts +53 -0
- package/registry/ui/lib/menu-motion.ts +187 -0
- package/registry/ui/lib/nm-class.ts +4 -0
- package/registry/ui/lib/nordman-ui-locale.ts +43 -0
- package/registry/ui/styles/interface-theme.css +196 -0
- package/registry/ui/styles/light-appearance.css +72 -0
- package/registry/ui/styles/tokens.css +424 -0
- package/registry/ui/styles/utilities.css +12 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NM_ACCENT_PRESETS,
|
|
3
|
+
NM_APPEARANCE_CONTRAST,
|
|
4
|
+
NM_APPEARANCE_DEFAULTS,
|
|
5
|
+
NM_APPEARANCE_MODES,
|
|
6
|
+
NM_APPEARANCE_STORAGE_KEY,
|
|
7
|
+
NM_UI_FONT_SIZE_PX,
|
|
8
|
+
NM_UI_FONT_WEIGHT,
|
|
9
|
+
NM_UI_ICON_SIZE_PX,
|
|
10
|
+
NM_UI_RADIUS_PERCENT,
|
|
11
|
+
NM_UI_SPACE_PERCENT,
|
|
12
|
+
findThemePreset,
|
|
13
|
+
type NmAccentPreset,
|
|
14
|
+
type NmAppearanceMode,
|
|
15
|
+
} from '../../config/nm-appearance.ts';
|
|
16
|
+
import { normalizeHexColor } from './color.ts';
|
|
17
|
+
|
|
18
|
+
export type NmAppearanceState = {
|
|
19
|
+
mode: NmAppearanceMode;
|
|
20
|
+
themePresetId: string;
|
|
21
|
+
accentPreset: NmAccentPreset;
|
|
22
|
+
customAccentHex: string;
|
|
23
|
+
backgroundHex: string;
|
|
24
|
+
foregroundHex: string;
|
|
25
|
+
uiFontSizePx: number;
|
|
26
|
+
uiFontWeight: number;
|
|
27
|
+
uiIconSizePx: number;
|
|
28
|
+
uiRadiusPercent: number;
|
|
29
|
+
uiSpacePercent: number;
|
|
30
|
+
contrast: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { NM_APPEARANCE_STORAGE_KEY };
|
|
34
|
+
|
|
35
|
+
function isMode(value: unknown): value is NmAppearanceMode {
|
|
36
|
+
return typeof value === 'string' && (NM_APPEARANCE_MODES as readonly string[]).includes(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isAccentPreset(value: unknown): value is NmAccentPreset {
|
|
40
|
+
return typeof value === 'string' && (NM_ACCENT_PRESETS as readonly string[]).includes(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function clampInt(value: unknown, min: number, max: number, fallback: number): number {
|
|
44
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
45
|
+
if (!Number.isFinite(n)) {
|
|
46
|
+
return fallback;
|
|
47
|
+
}
|
|
48
|
+
return Math.min(max, Math.max(min, Math.round(n)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function prefersDarkColorScheme(): boolean {
|
|
52
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveAppearanceScheme(
|
|
59
|
+
mode: NmAppearanceMode,
|
|
60
|
+
prefersDark: boolean = prefersDarkColorScheme(),
|
|
61
|
+
): 'light' | 'dark' {
|
|
62
|
+
if (mode === 'light') {
|
|
63
|
+
return 'light';
|
|
64
|
+
}
|
|
65
|
+
if (mode === 'dark') {
|
|
66
|
+
return 'dark';
|
|
67
|
+
}
|
|
68
|
+
return prefersDark ? 'dark' : 'light';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createDefaultAppearanceState(): NmAppearanceState {
|
|
72
|
+
const base: NmAppearanceState = {
|
|
73
|
+
mode: NM_APPEARANCE_DEFAULTS.mode,
|
|
74
|
+
themePresetId: NM_APPEARANCE_DEFAULTS.themePresetId,
|
|
75
|
+
accentPreset: NM_APPEARANCE_DEFAULTS.accentPreset,
|
|
76
|
+
customAccentHex: NM_APPEARANCE_DEFAULTS.customAccentHex,
|
|
77
|
+
backgroundHex: NM_APPEARANCE_DEFAULTS.backgroundHex,
|
|
78
|
+
foregroundHex: NM_APPEARANCE_DEFAULTS.foregroundHex,
|
|
79
|
+
uiFontSizePx: NM_APPEARANCE_DEFAULTS.uiFontSizePx,
|
|
80
|
+
uiFontWeight: NM_APPEARANCE_DEFAULTS.uiFontWeight,
|
|
81
|
+
uiIconSizePx: NM_APPEARANCE_DEFAULTS.uiIconSizePx,
|
|
82
|
+
uiRadiusPercent: NM_APPEARANCE_DEFAULTS.uiRadiusPercent,
|
|
83
|
+
uiSpacePercent: NM_APPEARANCE_DEFAULTS.uiSpacePercent,
|
|
84
|
+
contrast: NM_APPEARANCE_CONTRAST.default,
|
|
85
|
+
};
|
|
86
|
+
return applyThemePresetPalette(base);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function paletteForPreset(
|
|
90
|
+
themePresetId: string,
|
|
91
|
+
scheme: 'light' | 'dark',
|
|
92
|
+
): { backgroundHex: string; foregroundHex: string } {
|
|
93
|
+
const preset =
|
|
94
|
+
findThemePreset(themePresetId) ?? findThemePreset(NM_APPEARANCE_DEFAULTS.themePresetId)!;
|
|
95
|
+
const pair = scheme === 'dark' ? preset.dark : preset.light;
|
|
96
|
+
return {
|
|
97
|
+
backgroundHex: pair.canvas,
|
|
98
|
+
foregroundHex: pair.foreground,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Saved settings from before the website palette keep following the Nordman theme. */
|
|
103
|
+
const LEGACY_NORDMAN_COLORS: Readonly<Record<string, 'light' | 'dark'>> = {
|
|
104
|
+
'#000000/#ffffff': 'dark',
|
|
105
|
+
'#fcfaf6/#0a0a0a': 'light',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function currentNordmanColors(
|
|
109
|
+
themePresetId: string,
|
|
110
|
+
backgroundHex: string,
|
|
111
|
+
foregroundHex: string,
|
|
112
|
+
): { backgroundHex: string; foregroundHex: string } {
|
|
113
|
+
const key = `${backgroundHex}/${foregroundHex}`.toLowerCase();
|
|
114
|
+
const scheme = LEGACY_NORDMAN_COLORS[key];
|
|
115
|
+
if (themePresetId !== NM_APPEARANCE_DEFAULTS.themePresetId || scheme == null) {
|
|
116
|
+
return { backgroundHex, foregroundHex };
|
|
117
|
+
}
|
|
118
|
+
return paletteForPreset(themePresetId, scheme);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parseAppearanceState(raw: unknown): NmAppearanceState {
|
|
122
|
+
const defaults = createDefaultAppearanceState();
|
|
123
|
+
if (!raw || typeof raw !== 'object') {
|
|
124
|
+
return defaults;
|
|
125
|
+
}
|
|
126
|
+
const record = raw as Record<string, unknown>;
|
|
127
|
+
const themePresetId =
|
|
128
|
+
typeof record.themePresetId === 'string' && findThemePreset(record.themePresetId)
|
|
129
|
+
? record.themePresetId
|
|
130
|
+
: defaults.themePresetId;
|
|
131
|
+
|
|
132
|
+
const colors = currentNordmanColors(
|
|
133
|
+
themePresetId,
|
|
134
|
+
normalizeHexColor(
|
|
135
|
+
typeof record.backgroundHex === 'string' ? record.backgroundHex : defaults.backgroundHex,
|
|
136
|
+
defaults.backgroundHex,
|
|
137
|
+
),
|
|
138
|
+
normalizeHexColor(
|
|
139
|
+
typeof record.foregroundHex === 'string' ? record.foregroundHex : defaults.foregroundHex,
|
|
140
|
+
defaults.foregroundHex,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
mode: isMode(record.mode) ? record.mode : defaults.mode,
|
|
146
|
+
themePresetId,
|
|
147
|
+
accentPreset: isAccentPreset(record.accentPreset) ? record.accentPreset : defaults.accentPreset,
|
|
148
|
+
customAccentHex: normalizeHexColor(
|
|
149
|
+
typeof record.customAccentHex === 'string'
|
|
150
|
+
? record.customAccentHex
|
|
151
|
+
: defaults.customAccentHex,
|
|
152
|
+
defaults.customAccentHex,
|
|
153
|
+
),
|
|
154
|
+
...colors,
|
|
155
|
+
uiFontSizePx: clampInt(
|
|
156
|
+
record.uiFontSizePx,
|
|
157
|
+
NM_UI_FONT_SIZE_PX.min,
|
|
158
|
+
NM_UI_FONT_SIZE_PX.max,
|
|
159
|
+
defaults.uiFontSizePx,
|
|
160
|
+
),
|
|
161
|
+
uiFontWeight: Math.round(
|
|
162
|
+
clampInt(record.uiFontWeight, NM_UI_FONT_WEIGHT.min, NM_UI_FONT_WEIGHT.max, defaults.uiFontWeight) / 100,
|
|
163
|
+
) * 100,
|
|
164
|
+
uiIconSizePx: clampInt(
|
|
165
|
+
record.uiIconSizePx,
|
|
166
|
+
NM_UI_ICON_SIZE_PX.min,
|
|
167
|
+
NM_UI_ICON_SIZE_PX.max,
|
|
168
|
+
defaults.uiIconSizePx,
|
|
169
|
+
),
|
|
170
|
+
uiRadiusPercent: clampInt(
|
|
171
|
+
record.uiRadiusPercent,
|
|
172
|
+
NM_UI_RADIUS_PERCENT.min,
|
|
173
|
+
NM_UI_RADIUS_PERCENT.max,
|
|
174
|
+
defaults.uiRadiusPercent,
|
|
175
|
+
),
|
|
176
|
+
uiSpacePercent: clampInt(
|
|
177
|
+
record.uiSpacePercent,
|
|
178
|
+
NM_UI_SPACE_PERCENT.min,
|
|
179
|
+
NM_UI_SPACE_PERCENT.max,
|
|
180
|
+
defaults.uiSpacePercent,
|
|
181
|
+
),
|
|
182
|
+
contrast: clampInt(
|
|
183
|
+
record.contrast,
|
|
184
|
+
NM_APPEARANCE_CONTRAST.min,
|
|
185
|
+
NM_APPEARANCE_CONTRAST.max,
|
|
186
|
+
defaults.contrast,
|
|
187
|
+
),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function readStoredAppearanceState(): NmAppearanceState | null {
|
|
192
|
+
if (typeof window === 'undefined') {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const raw = window.localStorage.getItem(NM_APPEARANCE_STORAGE_KEY);
|
|
197
|
+
if (!raw) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return parseAppearanceState(JSON.parse(raw));
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function writeStoredAppearanceState(state: NmAppearanceState): void {
|
|
207
|
+
if (typeof window === 'undefined') {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
window.localStorage.setItem(NM_APPEARANCE_STORAGE_KEY, JSON.stringify(state));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function mergeThemePresetColors(
|
|
214
|
+
state: NmAppearanceState,
|
|
215
|
+
scheme: 'light' | 'dark',
|
|
216
|
+
): NmAppearanceState {
|
|
217
|
+
// The Custom theme keeps whatever colours the person chose.
|
|
218
|
+
if (state.themePresetId === 'custom') {
|
|
219
|
+
return state;
|
|
220
|
+
}
|
|
221
|
+
const { backgroundHex, foregroundHex } = paletteForPreset(state.themePresetId, scheme);
|
|
222
|
+
const preset = findThemePreset(state.themePresetId);
|
|
223
|
+
const next: NmAppearanceState = {
|
|
224
|
+
...state,
|
|
225
|
+
backgroundHex: normalizeHexColor(backgroundHex, state.backgroundHex),
|
|
226
|
+
foregroundHex: normalizeHexColor(foregroundHex, state.foregroundHex),
|
|
227
|
+
};
|
|
228
|
+
if (next.accentPreset === 'default' && preset) {
|
|
229
|
+
next.customAccentHex = normalizeHexColor(preset.swatch, next.customAccentHex);
|
|
230
|
+
}
|
|
231
|
+
return next;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Sync background and foreground (and default accent) from the active theme preset + mode. */
|
|
235
|
+
export function applyThemePresetPalette(
|
|
236
|
+
state: NmAppearanceState,
|
|
237
|
+
prefersDark: boolean = prefersDarkColorScheme(),
|
|
238
|
+
): NmAppearanceState {
|
|
239
|
+
return mergeThemePresetColors(state, resolveAppearanceScheme(state.mode, prefersDark));
|
|
240
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { NmAppearanceState } from './appearanceState.ts';
|
|
2
|
+
import { NM_APPEARANCE_APPLIED_EVENT, NM_APPEARANCE_CSS_VARS } from '../../config/nm-appearance.ts';
|
|
3
|
+
import { resolveAppearanceScheme } from './appearanceState.ts';
|
|
4
|
+
import { deriveAppearanceTokens } from './deriveTokens.ts';
|
|
5
|
+
import { createLogger } from '../../kit-logger.ts';
|
|
6
|
+
|
|
7
|
+
const logger = createLogger('interface-appearance-apply');
|
|
8
|
+
|
|
9
|
+
function prefersDarkColorScheme(): boolean {
|
|
10
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function clearAppliedAppearance(root: HTMLElement = document.documentElement): void {
|
|
17
|
+
NM_APPEARANCE_CSS_VARS.forEach((property) => {
|
|
18
|
+
root.style.removeProperty(property);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function applyAppearanceState(
|
|
23
|
+
state: NmAppearanceState,
|
|
24
|
+
root: HTMLElement = document.documentElement,
|
|
25
|
+
): { scheme: 'light' | 'dark' } {
|
|
26
|
+
const scheme = resolveAppearanceScheme(state.mode, prefersDarkColorScheme());
|
|
27
|
+
const tokens = deriveAppearanceTokens(state, scheme);
|
|
28
|
+
logger.debug('apply', 'appearance', { mode: state.mode, scheme, theme: state.themePresetId });
|
|
29
|
+
Object.entries(tokens).forEach(([property, value]) => {
|
|
30
|
+
root.style.setProperty(property, value);
|
|
31
|
+
});
|
|
32
|
+
if (typeof window !== 'undefined') {
|
|
33
|
+
window.dispatchEvent(new CustomEvent(NM_APPEARANCE_APPLIED_EVENT));
|
|
34
|
+
}
|
|
35
|
+
return { scheme };
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CloudAppearance } from 'orb-ui';
|
|
2
|
+
import { mixHex } from './color.ts';
|
|
3
|
+
|
|
4
|
+
export function deriveCloudOrbAppearance(
|
|
5
|
+
accent: string,
|
|
6
|
+
canvas: string,
|
|
7
|
+
fg: string,
|
|
8
|
+
): CloudAppearance {
|
|
9
|
+
return {
|
|
10
|
+
deepColor: mixHex(accent, canvas, 0.55),
|
|
11
|
+
upperColor: mixHex(accent, fg, 0.28),
|
|
12
|
+
lowerColor: mixHex(accent, canvas, 0.12),
|
|
13
|
+
highlightColor: mixHex(accent, fg, 0.62),
|
|
14
|
+
launchColor: mixHex(accent, canvas, 0.38),
|
|
15
|
+
spinnerColor: accent,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function cloudOrbAppearanceCssVars(appearance: CloudAppearance): Record<string, string> {
|
|
20
|
+
return {
|
|
21
|
+
'--orb-ui-cloud-appearance-deepColor': appearance.deepColor,
|
|
22
|
+
'--orb-ui-cloud-appearance-upperColor': appearance.upperColor,
|
|
23
|
+
'--orb-ui-cloud-appearance-lowerColor': appearance.lowerColor,
|
|
24
|
+
'--orb-ui-cloud-appearance-highlightColor': appearance.highlightColor,
|
|
25
|
+
'--orb-ui-cloud-appearance-launchColor': appearance.launchColor,
|
|
26
|
+
'--orb-ui-cloud-appearance-spinnerColor': appearance.spinnerColor,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deriveCloudOrbAppearanceTokens(
|
|
31
|
+
accent: string,
|
|
32
|
+
canvas: string,
|
|
33
|
+
fg: string,
|
|
34
|
+
): Record<string, string> {
|
|
35
|
+
return cloudOrbAppearanceCssVars(deriveCloudOrbAppearance(accent, canvas, fg));
|
|
36
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const HEX_PATTERN = /^#([0-9a-fA-F]{6})$/;
|
|
2
|
+
|
|
3
|
+
export function isHexColor(value: string): boolean {
|
|
4
|
+
return HEX_PATTERN.test(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeHexColor(value: string, fallback: string): string {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
if (!trimmed.startsWith('#')) {
|
|
10
|
+
const withHash = `#${trimmed}`;
|
|
11
|
+
if (isHexColor(withHash)) {
|
|
12
|
+
return withHash.toUpperCase();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (isHexColor(trimmed)) {
|
|
16
|
+
return trimmed.toUpperCase();
|
|
17
|
+
}
|
|
18
|
+
return fallback.toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hexToRgbTriplet(hex: string): string {
|
|
22
|
+
const normalized = normalizeHexColor(hex, '#000000').slice(1);
|
|
23
|
+
const r = Number.parseInt(normalized.slice(0, 2), 16);
|
|
24
|
+
const g = Number.parseInt(normalized.slice(2, 4), 16);
|
|
25
|
+
const b = Number.parseInt(normalized.slice(4, 6), 16);
|
|
26
|
+
return `${r} ${g} ${b}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function channel(value: number): number {
|
|
30
|
+
return Math.min(255, Math.max(0, Math.round(value)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Mix hex `color` toward `target` by amount in [0, 1]. */
|
|
34
|
+
export function mixHex(color: string, target: string, amount: number): string {
|
|
35
|
+
const a = normalizeHexColor(color, '#000000').slice(1);
|
|
36
|
+
const b = normalizeHexColor(target, '#000000').slice(1);
|
|
37
|
+
const t = Math.min(1, Math.max(0, amount));
|
|
38
|
+
const parse = (hex: string, start: number) => Number.parseInt(hex.slice(start, start + 2), 16);
|
|
39
|
+
const ar = parse(a, 0);
|
|
40
|
+
const ag = parse(a, 2);
|
|
41
|
+
const ab = parse(a, 4);
|
|
42
|
+
const br = parse(b, 0);
|
|
43
|
+
const bg = parse(b, 2);
|
|
44
|
+
const bb = parse(b, 4);
|
|
45
|
+
const r = channel(ar + (br - ar) * t);
|
|
46
|
+
const g = channel(ag + (bg - ag) * t);
|
|
47
|
+
const bl = channel(ab + (bb - ab) * t);
|
|
48
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${bl.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function relativeLuminance(hex: string): number {
|
|
52
|
+
const [r, g, b] = hexToRgbTriplet(hex).split(' ').map((value) => {
|
|
53
|
+
const unit = Number(value) / 255;
|
|
54
|
+
return unit <= 0.03928 ? unit / 12.92 : ((unit + 0.055) / 1.055) ** 2.4;
|
|
55
|
+
});
|
|
56
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** WCAG contrast ratio between two colours, from 1 to 21. */
|
|
60
|
+
export function contrastRatio(first: string, second: string): number {
|
|
61
|
+
const a = relativeLuminance(first);
|
|
62
|
+
const b = relativeLuminance(second);
|
|
63
|
+
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function hexToHsl(hex: string): { hue: number; saturation: number; lightness: number } {
|
|
67
|
+
const [r, g, b] = hexToRgbTriplet(hex).split(' ').map((value) => Number(value) / 255);
|
|
68
|
+
const max = Math.max(r, g, b);
|
|
69
|
+
const min = Math.min(r, g, b);
|
|
70
|
+
const delta = max - min;
|
|
71
|
+
const lightness = (max + min) / 2;
|
|
72
|
+
if (delta === 0) {
|
|
73
|
+
return { hue: 0, saturation: 0, lightness };
|
|
74
|
+
}
|
|
75
|
+
const saturation = delta / (1 - Math.abs(2 * lightness - 1));
|
|
76
|
+
let sector = (r - g) / delta + 4;
|
|
77
|
+
if (max === r) {
|
|
78
|
+
sector = ((g - b) / delta + 6) % 6;
|
|
79
|
+
} else if (max === g) {
|
|
80
|
+
sector = (b - r) / delta + 2;
|
|
81
|
+
}
|
|
82
|
+
return { hue: sector * 60, saturation, lightness };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function hslToHex(hue: number, saturation: number, lightness: number): string {
|
|
86
|
+
const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
|
|
87
|
+
const part = (offset: number): number => {
|
|
88
|
+
const turn = (offset + hue / 30) % 12;
|
|
89
|
+
const value = lightness - (chroma / 2) * Math.max(-1, Math.min(turn - 3, 9 - turn, 1));
|
|
90
|
+
return channel(value * 255);
|
|
91
|
+
};
|
|
92
|
+
const hex = [part(0), part(8), part(4)]
|
|
93
|
+
.map((value) => value.toString(16).padStart(2, '0'))
|
|
94
|
+
.join('');
|
|
95
|
+
return `#${hex}`.toUpperCase();
|
|
96
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { NmAppearanceState } from './appearanceState.ts';
|
|
2
|
+
import {
|
|
3
|
+
NM_ACCENT_HEX,
|
|
4
|
+
NM_APPEARANCE_CONTRAST,
|
|
5
|
+
NM_THEME_PRESETS,
|
|
6
|
+
NM_UI_FONT_SIZE_PX,
|
|
7
|
+
NM_UI_FONT_WEIGHT,
|
|
8
|
+
NM_UI_ICON_SIZE_PX,
|
|
9
|
+
NM_UI_RADIUS_PERCENT,
|
|
10
|
+
NM_UI_SPACE_PERCENT,
|
|
11
|
+
findThemePreset,
|
|
12
|
+
} from '../../config/nm-appearance.ts';
|
|
13
|
+
import { NM_SITE_PALETTE } from '../../config/nm-site-palette.ts';
|
|
14
|
+
import { deriveCloudOrbAppearanceTokens } from './cloudOrbAppearance.ts';
|
|
15
|
+
import { hexToRgbTriplet, mixHex, normalizeHexColor } from './color.ts';
|
|
16
|
+
import { deriveMarkTokens } from './markTokens.ts';
|
|
17
|
+
import { NM_RADIUS_SCALE_VAR } from '../../config/nm-radius.ts';
|
|
18
|
+
|
|
19
|
+
export type NmDerivedAppearanceTokens = Record<string, string>;
|
|
20
|
+
|
|
21
|
+
/** The accent colour that is in effect for these settings. */
|
|
22
|
+
export function resolveAccentHex(state: NmAppearanceState): string {
|
|
23
|
+
if (state.accentPreset === 'custom') {
|
|
24
|
+
return normalizeHexColor(state.customAccentHex, '#D2BB9C');
|
|
25
|
+
}
|
|
26
|
+
if (state.accentPreset === 'default' && state.themePresetId === 'custom') {
|
|
27
|
+
return normalizeHexColor(state.customAccentHex, '#D2BB9C');
|
|
28
|
+
}
|
|
29
|
+
if (state.accentPreset === 'default') {
|
|
30
|
+
const preset = findThemePreset(state.themePresetId);
|
|
31
|
+
return preset?.swatch ?? NM_THEME_PRESETS[0].swatch;
|
|
32
|
+
}
|
|
33
|
+
return NM_ACCENT_HEX[state.accentPreset];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Contrast adjusts container surface mix strength only (0 = subtle, max = strong). */
|
|
37
|
+
function surfaceMixStrength(contrast: number, scheme: 'light' | 'dark'): number {
|
|
38
|
+
const t = contrast / NM_APPEARANCE_CONTRAST.max;
|
|
39
|
+
const min = scheme === 'dark' ? 0.06 : 0.05;
|
|
40
|
+
const max = scheme === 'dark' ? 0.42 : 0.38;
|
|
41
|
+
return min + t * (max - min);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function deriveIconTokens(uiIconSizePx: number): Record<string, string> {
|
|
45
|
+
const iconPx = `${uiIconSizePx}px`;
|
|
46
|
+
const smPx = Math.max(NM_UI_ICON_SIZE_PX.min, uiIconSizePx - 4);
|
|
47
|
+
return {
|
|
48
|
+
'--nm-icon-button': iconPx,
|
|
49
|
+
'--nm-icon-sm': `${smPx}px`,
|
|
50
|
+
'--nm-rail-icon': iconPx,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function deriveUiControlTokens(uiFontSizePx: number, uiIconSizePx: number): Record<string, string> {
|
|
55
|
+
const padPx = 8;
|
|
56
|
+
const iconControlPx = uiIconSizePx + padPx * 2;
|
|
57
|
+
const chipMinPx = Math.max(iconControlPx, uiFontSizePx + padPx * 2);
|
|
58
|
+
return {
|
|
59
|
+
'--nm-ui-chip-min-height': `${chipMinPx}px`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deriveRadiusScale(uiRadiusPercent: number): Record<string, string> {
|
|
64
|
+
const scale = uiRadiusPercent / NM_UI_RADIUS_PERCENT.default;
|
|
65
|
+
return {
|
|
66
|
+
[NM_RADIUS_SCALE_VAR]: String(scale),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function deriveSpaceScale(uiSpacePercent: number): Record<string, string> {
|
|
71
|
+
return {
|
|
72
|
+
'--nm-space-scale': String(uiSpacePercent / NM_UI_SPACE_PERCENT.default),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The setting is Regular. The heavier weights step up a hundred each, and light steps down. */
|
|
77
|
+
function deriveWeightTokens(base: number): Record<string, string> {
|
|
78
|
+
const step = (by: number) => String(Math.min(NM_UI_FONT_WEIGHT.max, Math.max(NM_UI_FONT_WEIGHT.min, base + by)));
|
|
79
|
+
return {
|
|
80
|
+
'--nm-weight-light': step(-100),
|
|
81
|
+
'--nm-weight-regular': step(0),
|
|
82
|
+
'--nm-weight-medium': step(100),
|
|
83
|
+
'--nm-weight-semibold': step(200),
|
|
84
|
+
'--nm-weight-bold': step(300),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function deriveTypeTokens(uiFontSizePx: number): Record<string, string> {
|
|
89
|
+
const uiScale = uiFontSizePx / NM_UI_FONT_SIZE_PX.default;
|
|
90
|
+
const bodyPx = `${uiFontSizePx}px`;
|
|
91
|
+
const uiSmPx = `${Math.max(NM_UI_FONT_SIZE_PX.min, uiFontSizePx - 2)}px`;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
'--nm-type-body': bodyPx,
|
|
95
|
+
'--nm-type-ui': bodyPx,
|
|
96
|
+
'--control-fs': bodyPx,
|
|
97
|
+
'--nm-type-ui-sm': uiSmPx,
|
|
98
|
+
'--nm-type-lede': `${uiScale * 1.5}rem`,
|
|
99
|
+
'--nm-type-section': `${uiScale * 2.5}rem`,
|
|
100
|
+
'--nm-type-card': `${uiScale * 2}rem`,
|
|
101
|
+
'--nm-type-caption': `${uiScale * 0.75}rem`,
|
|
102
|
+
'--markdown-font-size': bodyPx,
|
|
103
|
+
'--font-size-base': bodyPx,
|
|
104
|
+
'--font-size-sm': uiSmPx,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Overrides ThemeProvider inline colors so Chat chrome uses appearance SSOT. */
|
|
109
|
+
function deriveLibreChatBridge(
|
|
110
|
+
tokens: NmDerivedAppearanceTokens,
|
|
111
|
+
accent: string,
|
|
112
|
+
): Record<string, string> {
|
|
113
|
+
const accentRgb = hexToRgbTriplet(accent);
|
|
114
|
+
return {
|
|
115
|
+
'--text-primary': tokens['--nm-fg-rgb']!,
|
|
116
|
+
'--text-secondary': tokens['--nm-fg-subtle-rgb']!,
|
|
117
|
+
'--text-tertiary': tokens['--nm-stroke-rgb']!,
|
|
118
|
+
'--text-muted': tokens['--nm-fg-muted-rgb']!,
|
|
119
|
+
'--presentation': tokens['--nm-canvas-rgb']!,
|
|
120
|
+
'--surface-primary': tokens['--nm-canvas-rgb']!,
|
|
121
|
+
'--surface-chat': tokens['--nm-canvas-rgb']!,
|
|
122
|
+
'--surface-secondary': tokens['--nm-panel-rgb']!,
|
|
123
|
+
'--surface-secondary-alt': tokens['--nm-line-rgb']!,
|
|
124
|
+
'--surface-tertiary': tokens['--nm-raised-rgb']!,
|
|
125
|
+
'--surface-primary-alt': tokens['--nm-raised-rgb']!,
|
|
126
|
+
'--surface-dialog': tokens['--nm-panel-rgb']!,
|
|
127
|
+
'--surface-hover': tokens['--nm-panel-rgb']!,
|
|
128
|
+
'--surface-active': tokens['--nm-line-rgb']!,
|
|
129
|
+
'--border-light': tokens['--nm-line-rgb']!,
|
|
130
|
+
'--border-medium': tokens['--nm-stroke-rgb']!,
|
|
131
|
+
'--link': accentRgb,
|
|
132
|
+
'--link-hover': accentRgb,
|
|
133
|
+
'--link-visited': accentRgb,
|
|
134
|
+
'--accent-primary': accentRgb,
|
|
135
|
+
'--ring-primary': accentRgb,
|
|
136
|
+
'--brand-purple': accentRgb,
|
|
137
|
+
'--nordman-accent': accentRgb,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The untouched Nordman theme shows the website palette; anything else mixes its own surfaces. */
|
|
142
|
+
function usesSitePalette(
|
|
143
|
+
state: NmAppearanceState,
|
|
144
|
+
scheme: 'light' | 'dark',
|
|
145
|
+
canvas: string,
|
|
146
|
+
fg: string,
|
|
147
|
+
): boolean {
|
|
148
|
+
const site = NM_SITE_PALETTE[scheme];
|
|
149
|
+
return (
|
|
150
|
+
state.themePresetId === NM_THEME_PRESETS[0].id &&
|
|
151
|
+
state.contrast === NM_APPEARANCE_CONTRAST.default &&
|
|
152
|
+
canvas.toLowerCase() === site.canvas &&
|
|
153
|
+
fg.toLowerCase() === site.fg
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function deriveAppearanceTokens(
|
|
158
|
+
state: NmAppearanceState,
|
|
159
|
+
scheme: 'light' | 'dark',
|
|
160
|
+
): NmDerivedAppearanceTokens {
|
|
161
|
+
const canvas = normalizeHexColor(state.backgroundHex, '#000000');
|
|
162
|
+
const fg = normalizeHexColor(state.foregroundHex, '#ffffff');
|
|
163
|
+
const accent = resolveAccentHex(state);
|
|
164
|
+
const mix = surfaceMixStrength(state.contrast, scheme);
|
|
165
|
+
|
|
166
|
+
const site = usesSitePalette(state, scheme, canvas, fg) ? NM_SITE_PALETTE[scheme] : null;
|
|
167
|
+
const panel = site?.panel ?? mixHex(canvas, fg, mix);
|
|
168
|
+
const raised = site?.raised ?? mixHex(canvas, fg, mix * 0.72);
|
|
169
|
+
const inset = site?.inset ?? mixHex(canvas, fg, mix * 0.85);
|
|
170
|
+
const line = site?.line ?? mixHex(canvas, fg, Math.min(0.55, mix + 0.12));
|
|
171
|
+
const lineStrong = site?.lineStrong ?? mixHex(canvas, fg, Math.min(0.6, mix + 0.17));
|
|
172
|
+
const stroke = site?.stroke ?? mixHex(canvas, fg, Math.min(0.65, mix + 0.22));
|
|
173
|
+
const fgMuted = site?.muted ?? mixHex(fg, canvas, 0.22);
|
|
174
|
+
const fgSubtle = site?.subtle ?? mixHex(fg, canvas, 0.38);
|
|
175
|
+
|
|
176
|
+
const nmTokens: NmDerivedAppearanceTokens = {
|
|
177
|
+
'--nm-canvas': canvas,
|
|
178
|
+
'--nm-fg': fg,
|
|
179
|
+
'--nm-fg-muted': fgMuted,
|
|
180
|
+
'--nm-fg-subtle': fgSubtle,
|
|
181
|
+
'--nm-stroke': stroke,
|
|
182
|
+
'--nm-line': line,
|
|
183
|
+
'--nm-line-strong': lineStrong,
|
|
184
|
+
'--nm-divider': mixHex(canvas, fg, (state.contrast / NM_APPEARANCE_CONTRAST.max) * 0.5),
|
|
185
|
+
'--nm-panel': panel,
|
|
186
|
+
'--nm-raised': raised,
|
|
187
|
+
'--nm-inset': inset,
|
|
188
|
+
'--nm-canvas-rgb': hexToRgbTriplet(canvas),
|
|
189
|
+
'--nm-fg-rgb': hexToRgbTriplet(fg),
|
|
190
|
+
'--nm-fg-muted-rgb': hexToRgbTriplet(fgMuted),
|
|
191
|
+
'--nm-fg-subtle-rgb': hexToRgbTriplet(fgSubtle),
|
|
192
|
+
'--nm-stroke-rgb': hexToRgbTriplet(stroke),
|
|
193
|
+
'--nm-line-rgb': hexToRgbTriplet(line),
|
|
194
|
+
'--nm-panel-rgb': hexToRgbTriplet(panel),
|
|
195
|
+
'--nm-raised-rgb': hexToRgbTriplet(raised),
|
|
196
|
+
'--nm-inset-rgb': hexToRgbTriplet(inset),
|
|
197
|
+
'--sand': accent,
|
|
198
|
+
'--nm-accent': accent,
|
|
199
|
+
'--nm-accent-rgb': hexToRgbTriplet(accent),
|
|
200
|
+
...deriveCloudOrbAppearanceTokens(accent, canvas, fg),
|
|
201
|
+
...deriveMarkTokens(accent, canvas, fg),
|
|
202
|
+
...deriveTypeTokens(state.uiFontSizePx),
|
|
203
|
+
...deriveWeightTokens(state.uiFontWeight),
|
|
204
|
+
...deriveIconTokens(state.uiIconSizePx),
|
|
205
|
+
...deriveUiControlTokens(state.uiFontSizePx, state.uiIconSizePx),
|
|
206
|
+
...deriveRadiusScale(state.uiRadiusPercent),
|
|
207
|
+
...deriveSpaceScale(state.uiSpacePercent),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
...nmTokens,
|
|
212
|
+
...deriveLibreChatBridge(nmTokens, accent),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function themeModeFromAppearance(mode: NmAppearanceState['mode']): string {
|
|
217
|
+
return mode;
|
|
218
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { contrastRatio, hexToHsl, hslToHex, mixHex } from './color.ts';
|
|
2
|
+
|
|
3
|
+
/** Text smaller than 18px needs 4.5:1. The margin covers rounding in the mix. */
|
|
4
|
+
const MARK_MIN_CONTRAST = 4.6;
|
|
5
|
+
const MARK_SURFACE_MIX = 0.09;
|
|
6
|
+
const MARK_HUE_STEP = 120;
|
|
7
|
+
const MARK_SATURATION = { min: 0.4, max: 0.7 };
|
|
8
|
+
const MARK_LIGHTNESS_STEP = 0.02;
|
|
9
|
+
|
|
10
|
+
function readableMark(
|
|
11
|
+
hue: number,
|
|
12
|
+
saturation: number,
|
|
13
|
+
backgrounds: readonly string[],
|
|
14
|
+
fg: string,
|
|
15
|
+
): string {
|
|
16
|
+
const canvas = backgrounds[0] ?? '#000000';
|
|
17
|
+
const darkCanvas = contrastRatio(canvas, '#FFFFFF') > contrastRatio(canvas, '#000000');
|
|
18
|
+
const direction = darkCanvas ? 1 : -1;
|
|
19
|
+
for (
|
|
20
|
+
let lightness = darkCanvas ? 0.68 : 0.42;
|
|
21
|
+
lightness >= 0 && lightness <= 1;
|
|
22
|
+
lightness += direction * MARK_LIGHTNESS_STEP
|
|
23
|
+
) {
|
|
24
|
+
const color = hslToHex(hue, saturation, lightness);
|
|
25
|
+
if (backgrounds.every((background) => contrastRatio(color, background) >= MARK_MIN_CONTRAST)) {
|
|
26
|
+
return color;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return fg;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Three text highlights from the accent's hue and its two even neighbours, readable on the theme. */
|
|
33
|
+
export function deriveMarkTokens(
|
|
34
|
+
accent: string,
|
|
35
|
+
canvas: string,
|
|
36
|
+
fg: string,
|
|
37
|
+
): Record<string, string> {
|
|
38
|
+
const surface = mixHex(canvas, fg, MARK_SURFACE_MIX);
|
|
39
|
+
const { hue, saturation } = hexToHsl(accent);
|
|
40
|
+
const vivid = Math.min(MARK_SATURATION.max, Math.max(MARK_SATURATION.min, saturation));
|
|
41
|
+
const mark = (offset: number) =>
|
|
42
|
+
readableMark((hue + offset) % 360, vivid, [canvas, surface], fg);
|
|
43
|
+
return {
|
|
44
|
+
'--nm-mark-primary': mark(0),
|
|
45
|
+
'--nm-mark-secondary': mark(MARK_HUE_STEP),
|
|
46
|
+
'--nm-mark-tertiary': mark(MARK_HUE_STEP * 2),
|
|
47
|
+
'--nm-mark-surface': surface,
|
|
48
|
+
};
|
|
49
|
+
}
|