├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── package.json ├── packages ├── api │ ├── README.md │ └── package.json ├── db │ ├── README.md │ └── package.json ├── ui.deprecated │ ├── .gitignore │ ├── .npmrc │ ├── DISTRIBUTION.md │ ├── README.md │ ├── bin │ │ └── org-setup.sh │ ├── bunfig.toml │ ├── examples │ │ ├── backend │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ ├── package.json │ │ │ └── sample.env │ │ ├── button │ │ │ ├── Button.svelte │ │ │ └── button.html │ │ ├── camera-subscribe-example │ │ │ ├── README.md │ │ │ ├── css │ │ │ │ └── styles.css │ │ │ ├── index.html │ │ │ └── js │ │ │ │ ├── app.js │ │ │ │ ├── camera.js │ │ │ │ ├── subscribe.js │ │ │ │ └── sync-service.js │ │ ├── div │ │ │ ├── Div.svelte │ │ │ └── div.html │ │ ├── form-example │ │ │ ├── README.md │ │ │ ├── abstraction-example.html │ │ │ └── index.html │ │ ├── kde-example │ │ │ └── index.html │ │ ├── section │ │ │ ├── Section.svelte │ │ │ └── section.html │ │ └── ui-elements │ │ │ ├── README.md │ │ │ └── index.html │ ├── favicon.ico │ ├── favicon.png │ ├── index.html │ ├── ios │ │ ├── README.md │ │ ├── WKWebViewHandler.swift │ │ └── WebViewViewController.swift │ ├── logo.png │ ├── logo.svg │ ├── native │ │ ├── kde │ │ │ ├── README.md │ │ │ ├── main.cpp │ │ │ ├── mainwindow.cpp │ │ │ ├── mainwindow.h │ │ │ ├── resources.qrc │ │ │ ├── sans-ui-kde.pro │ │ │ ├── webbridge.cpp │ │ │ └── webbridge.h │ │ └── qt │ │ │ ├── main.cpp │ │ │ ├── mainwindow.cpp │ │ │ ├── mainwindow.h │ │ │ ├── resources.qrc │ │ │ ├── sans-ui-qt.pro │ │ │ ├── webbridge.cpp │ │ │ └── webbridge.h │ ├── package.json │ ├── pnpm-lock.yaml │ ├── releaase-package.yml │ ├── scripts │ │ ├── build-components.js │ │ └── build-deno.js │ ├── src │ │ ├── app.svelte │ │ ├── components │ │ │ ├── WebViewComponent.svelte │ │ │ └── html5 │ │ │ │ ├── A.svelte │ │ │ │ ├── AComponent.js │ │ │ │ ├── Article.svelte │ │ │ │ ├── ArticleComponent.js │ │ │ │ ├── Aside.svelte │ │ │ │ ├── AsideComponent.js │ │ │ │ ├── Button.svelte │ │ │ │ ├── ButtonComponent.js │ │ │ │ ├── Div.svelte │ │ │ │ ├── DivComponent.js │ │ │ │ ├── Footer.svelte │ │ │ │ ├── FooterComponent.js │ │ │ │ ├── FormComponent.js │ │ │ │ ├── H1.svelte │ │ │ │ ├── H1Component.js │ │ │ │ ├── Header.svelte │ │ │ │ ├── HeaderComponent.js │ │ │ │ ├── Img.svelte │ │ │ │ ├── ImgComponent.js │ │ │ │ ├── InputComponent.js │ │ │ │ ├── LabelComponent.js │ │ │ │ ├── Main.svelte │ │ │ │ ├── MainComponent.js │ │ │ │ ├── Nav.svelte │ │ │ │ ├── NavComponent.js │ │ │ │ ├── OptionComponent.js │ │ │ │ ├── P.svelte │ │ │ │ ├── PComponent.js │ │ │ │ ├── Section.svelte │ │ │ │ ├── SectionComponent.js │ │ │ │ ├── SelectComponent.js │ │ │ │ ├── TextareaComponent.js │ │ │ │ ├── a.html │ │ │ │ ├── article.html │ │ │ │ ├── aside.html │ │ │ │ ├── button.html │ │ │ │ ├── div.html │ │ │ │ ├── footer.html │ │ │ │ ├── h1.html │ │ │ │ ├── header.html │ │ │ │ ├── index.js │ │ │ │ ├── main.html │ │ │ │ ├── nav.html │ │ │ │ ├── p.html │ │ │ │ └── section.html │ │ ├── index.js │ │ └── lib │ │ │ ├── components │ │ │ ├── ComponentAdapter.js │ │ │ ├── ComponentFactory.js │ │ │ ├── README.md │ │ │ └── index.js │ │ │ └── webview │ │ │ ├── QtWebEngineBridge.js │ │ │ ├── WKWebViewBridge.js │ │ │ ├── WebViewAdapter.js │ │ │ └── types.d.ts │ ├── static │ │ └── logos │ │ │ ├── api │ │ │ ├── favicon.sans-api.svg │ │ │ ├── favicon.sans-api.white.svg │ │ │ ├── logo.sans-api.svg │ │ │ └── logo.sans-api.white.svg │ │ │ ├── db │ │ │ ├── favicon.db.svg │ │ │ ├── favicon.db.white.svg │ │ │ ├── logo.sans-db.svg │ │ │ └── logo.sans-db.white.svg │ │ │ └── ui │ │ │ ├── favicon.sans-ui.svg │ │ │ ├── favicon.sans-ui.white.svg │ │ │ ├── logo.sans-ui.svg │ │ │ └── logo.sans-ui.white.svg │ ├── tsconfig.json │ └── vite.config.js └── ui │ ├── .gitignore │ ├── DISTRIBUTION.md │ ├── README.md │ ├── bin │ └── org-setup.sh │ ├── bunfig.toml │ ├── examples │ ├── backend │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── sample.env │ ├── button │ │ ├── Button.svelte │ │ └── button.html │ ├── camera-subscribe-example │ │ ├── README.md │ │ ├── css │ │ │ └── styles.css │ │ ├── index.html │ │ └── js │ │ │ ├── app.js │ │ │ ├── camera.js │ │ │ ├── subscribe.js │ │ │ └── sync-service.js │ ├── common │ │ ├── common.js │ │ ├── navbar.html │ │ └── styles.css │ ├── desktop-example │ │ ├── index.html │ │ └── index.js │ ├── div │ │ ├── Div.svelte │ │ └── div.html │ ├── form-example │ │ ├── README.md │ │ ├── abstraction-example.html │ │ └── index.html │ ├── index.html │ ├── kde-example │ │ └── index.html │ ├── mobile-example │ │ ├── app.js │ │ └── index.html │ ├── native-ui-example │ │ └── index.html │ ├── section │ │ ├── Section.svelte │ │ └── section.html │ └── ui-elements │ │ ├── README.md │ │ └── index.html │ ├── favicon.ico │ ├── favicon.png │ ├── index.html │ ├── ios │ ├── README.md │ ├── WKWebViewHandler.swift │ └── WebViewViewController.swift │ ├── logo.png │ ├── logo.svg │ ├── native │ ├── kde │ │ ├── README.md │ │ ├── main.cpp │ │ ├── mainwindow.cpp │ │ ├── mainwindow.h │ │ ├── resources.qrc │ │ ├── sans-ui-kde.pro │ │ ├── webbridge.cpp │ │ └── webbridge.h │ └── qt │ │ ├── main.cpp │ │ ├── mainwindow.cpp │ │ ├── mainwindow.h │ │ ├── resources.qrc │ │ ├── sans-ui-qt.pro │ │ ├── webbridge.cpp │ │ └── webbridge.h │ ├── package.json │ ├── pnpm-lock.yaml │ ├── releaase-package.yml │ ├── scripts │ ├── build-components.js │ └── build-deno.js │ ├── src │ ├── app.svelte │ ├── components │ │ ├── WebViewComponent.svelte │ │ └── html5 │ │ │ ├── A.svelte │ │ │ ├── AComponent.js │ │ │ ├── Article.svelte │ │ │ ├── ArticleComponent.js │ │ │ ├── Aside.svelte │ │ │ ├── AsideComponent.js │ │ │ ├── Button.svelte │ │ │ ├── ButtonComponent.js │ │ │ ├── Div.svelte │ │ │ ├── DivComponent.js │ │ │ ├── Footer.svelte │ │ │ ├── FooterComponent.js │ │ │ ├── FormComponent.js │ │ │ ├── H1.svelte │ │ │ ├── H1Component.js │ │ │ ├── Header.svelte │ │ │ ├── HeaderComponent.js │ │ │ ├── Img.svelte │ │ │ ├── ImgComponent.js │ │ │ ├── InputComponent.js │ │ │ ├── LabelComponent.js │ │ │ ├── Main.svelte │ │ │ ├── MainComponent.js │ │ │ ├── Nav.svelte │ │ │ ├── NavComponent.js │ │ │ ├── OptionComponent.js │ │ │ ├── P.svelte │ │ │ ├── PComponent.js │ │ │ ├── Section.svelte │ │ │ ├── SectionComponent.js │ │ │ ├── SelectComponent.js │ │ │ ├── TextareaComponent.js │ │ │ ├── a.html │ │ │ ├── article.html │ │ │ ├── aside.html │ │ │ ├── button.html │ │ │ ├── div.html │ │ │ ├── footer.html │ │ │ ├── h1.html │ │ │ ├── header.html │ │ │ ├── index.js │ │ │ ├── main.html │ │ │ ├── nav.html │ │ │ ├── p.html │ │ │ └── section.html │ ├── index.js │ └── lib │ │ ├── components │ │ ├── ComponentAdapter.js │ │ ├── ComponentFactory.js │ │ ├── README.md │ │ └── index.js │ │ ├── native │ │ ├── NativeUIAdapter.js │ │ ├── desktop │ │ │ └── DesktopAdapter.js │ │ ├── mobile │ │ │ └── MobileAdapter.js │ │ └── web │ │ │ └── WebAdapter.js │ │ ├── platform │ │ └── PlatformDetector.js │ │ └── webview │ │ ├── QtWebEngineBridge.js │ │ ├── WKWebViewBridge.js │ │ ├── WebViewAdapter.js │ │ └── types.d.ts │ ├── static │ └── logos │ │ ├── api │ │ ├── favicon.sans-api.svg │ │ ├── favicon.sans-api.white.svg │ │ ├── logo.sans-api.svg │ │ └── logo.sans-api.white.svg │ │ ├── db │ │ ├── favicon.db.svg │ │ ├── favicon.db.white.svg │ │ ├── logo.sans-db.svg │ │ └── logo.sans-db.white.svg │ │ └── ui │ │ ├── favicon.sans-ui.svg │ │ ├── favicon.sans-ui.white.svg │ │ ├── logo.sans-ui.svg │ │ └── logo.sans-ui.white.svg │ ├── tsconfig.json │ ├── vite.config.js │ └── vite.docs.js └── publish-packages.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Dependency directories 11 | node_modules 12 | .npm 13 | 14 | # Build outputs 15 | dist 16 | dist-ssr 17 | *.local 18 | 19 | # Editor directories and files 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | .DS_Store 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | # Vite specific 31 | .vite/ 32 | 33 | # Environment variables 34 | .env 35 | .env.* 36 | !.env.example 37 | 38 | # Coverage directory used by tools like istanbul 39 | coverage 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional stylelint cache 48 | .stylelintcache 49 | 50 | # Yarn Integrity file 51 | .yarn-integrity 52 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. 4 | 5 | For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage. 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # ISC License 2 | 3 | Copyright (c) 2025, Profullstack, Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@profullstack/sans", 3 | "version": "1.0.0", 4 | "description": "Sans framework with UI, API, and DB components", 5 | "type": "module", 6 | "private": true, 7 | "workspaces": [ 8 | "ui", 9 | "api", 10 | "db" 11 | ], 12 | "scripts": { 13 | "build": "pnpm --filter ./ui build", 14 | "test": "pnpm --filter ./ui test", 15 | "lint": "pnpm --filter ./ui lint", 16 | "format": "pnpm --filter ./ui format" 17 | }, 18 | "keywords": [ 19 | "sans", 20 | "framework", 21 | "ui", 22 | "api", 23 | "db" 24 | ], 25 | "author": "Profullstack", 26 | "license": "ISC", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/profullstack/sans.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/profullstack/sans/issues" 33 | }, 34 | "homepage": "https://github.com/profullstack/sans" 35 | } 36 | -------------------------------------------------------------------------------- /packages/api/README.md: -------------------------------------------------------------------------------- 1 |

2 | Sans UI Logo 3 | Sans API Logo 4 | Sans DB Logo 5 |

6 | 7 | # Sans API 8 | 9 | A lightweight API framework for building backend services. 10 | 11 | ## Coming Soon 12 | 13 | The Sans API framework is currently under development. Stay tuned for updates! 14 | 15 | ## Features (Planned) 16 | 17 | - RESTful API support 18 | - GraphQL integration 19 | - Authentication and authorization 20 | - Rate limiting and caching 21 | - Middleware support 22 | - Documentation generation 23 | - Integration with Sans UI and Sans DB 24 | 25 | ## Integration with Sans Component Abstraction Layer 26 | 27 | Sans API will integrate seamlessly with the Sans UI component abstraction layer, allowing for easy communication between frontend and backend components. 28 | 29 | --- 30 | 31 | ## Connect With Us 32 | 33 | [![Reddit](https://img.shields.io/badge/Reddit-FF4500?style=for-the-badge&logo=reddit&logoColor=white)](https://www.reddit.com/r/sans_ui/) 34 | [![X](https://img.shields.io/badge/X-000000?style=for-the-badge&logo=x&logoColor=white)](https://x.com/profullstackinc) 35 | [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/profullstackinc) 36 | [![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/+VGCI_sR-guhmNTNh) 37 | [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/profullstackinc/shared_invite/zt-2d9c842fk-jo848We~tDajW9nn6DEggw) 38 | [![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/XXvzu4G4) 39 | [![GitHub](https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/profullstack) 40 | 41 | *Built happily using "Windsurf on Linux"* 42 | *Sponsored by [Profullstack, Inc.](https://profullstack.com)* 43 | -------------------------------------------------------------------------------- /packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@profullstack/sans-api", 3 | "version": "1.0.3", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "publishConfig": { 13 | "access": "public" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@profullstack/sans-db", 3 | "version": "1.0.3", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "publishConfig": { 13 | "access": "public" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/ui.deprecated/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Environment variables 5 | .env* 6 | 7 | # Build artifacts 8 | /dist/ 9 | /.svelte-kit/ 10 | /build/ 11 | 12 | # IDE and editor files 13 | .idea/ 14 | .vscode/ 15 | *.sublime-project 16 | *.sublime-workspace 17 | 18 | # OS files 19 | .DS_Store 20 | Thumbs.db 21 | 22 | # Logs 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | pnpm-debug.log* 27 | 28 | # Cache 29 | .cache/ 30 | .pnpm-store/ 31 | -------------------------------------------------------------------------------- /packages/ui.deprecated/.npmrc: -------------------------------------------------------------------------------- 1 | @profullstack:registry=https://npm.pkg.github.com -------------------------------------------------------------------------------- /packages/ui.deprecated/bunfig.toml: -------------------------------------------------------------------------------- 1 | # Sans UI Bun Configuration 2 | 3 | [install] 4 | dry-run = false 5 | frozen-lockfile = false 6 | 7 | [install.cache] 8 | dir = ".bun/install/cache" 9 | enabled = true 10 | 11 | [test] 12 | coverage = false 13 | 14 | [build] 15 | entrypoints = ["src/index.js"] 16 | outdir = "dist" 17 | splitting = true 18 | minify = true 19 | modules = true 20 | 21 | [publish] 22 | access = "public" 23 | scope = "profullstack" 24 | registry = "https://registry.npmjs.org/" 25 | -------------------------------------------------------------------------------- /packages/ui.deprecated/examples/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sans-ui-backend-example", 3 | "version": "1.0.0", 4 | "description": "Backend API example for Sans UI using Elysia and Mailgun", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js", 9 | "dev": "node --watch index.js" 10 | }, 11 | "keywords": [ 12 | "sans-ui", 13 | "elysia", 14 | "mailgun", 15 | "api" 16 | ], 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@elysiajs/cors": "^0.8.0", 21 | "@supabase/supabase-js": "^2.39.0", 22 | "dotenv": "^16.3.1", 23 | "elysia": "^0.8.9", 24 | "form-data": "^4.0.0", 25 | "mailgun.js": "^9.3.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui.deprecated/examples/backend/sample.env: -------------------------------------------------------------------------------- 1 | # API Configuration 2 | PORT=3001 3 | API_KEY=your_api_key_here 4 | 5 | # Mailgun Configuration 6 | MAILGUN_API_KEY=your_mailgun_api_key_here 7 | MAILGUN_DOMAIN=your_mailgun_domain.com 8 | EMAIL_FROM=Sans UI 9 | 10 | # Supabase Configuration 11 | SUPABASE_URL=https://your-project-id.supabase.co 12 | SUPABASE_KEY=your_supabase_anon_key_here 13 | SUPABASE_TABLE=subscribers 14 | 15 | # CORS Configuration 16 | ALLOW_ORIGINS=http://localhost:3000,http://localhost:8000 17 | -------------------------------------------------------------------------------- /packages/ui.deprecated/examples/ui-elements/README.md: -------------------------------------------------------------------------------- 1 |

2 | Sans UI Logo 3 | Sans API Logo 4 | Sans DB Logo 5 |

6 | 7 | # Sans UI Elements Showcase 8 | 9 | This example showcases all the available UI elements and components in the Sans UI library. It serves as a visual reference and documentation for developers using the Sans UI framework. 10 | 11 | ## Components Demonstrated 12 | 13 | ### Basic HTML Components 14 | - `sans-div`: Basic container component 15 | - `sans-p`: Paragraph component 16 | - `sans-h1`: Heading component 17 | - `sans-a`: Anchor/link component 18 | - `sans-img`: Image component 19 | - `sans-button`: Button component 20 | 21 | ### Form Components 22 | - `sans-form`: Form container component 23 | - `sans-input`: Input field component (text, number, checkbox, etc.) 24 | - `sans-select`: Dropdown select component 25 | - `sans-option`: Option component for select dropdowns 26 | - `sans-textarea`: Multi-line text input component 27 | - `sans-label`: Label component for form fields 28 | 29 | ### Layout Components 30 | - `sans-section`: Section container component 31 | - `sans-article`: Article container component 32 | - `sans-header`: Header component 33 | - `sans-footer`: Footer component 34 | 35 | ## Color Palette 36 | The example also demonstrates the standard color palette used in Sans UI: 37 | - Primary: #6200ee 38 | - Secondary: #03dac6 39 | - Error: #b00020 40 | - Warning: #ffab00 41 | - Info: #2196f3 42 | - Success: #4caf50 43 | 44 | ## Component Abstraction Layer 45 | The example includes a demonstration of the Component Abstraction Layer, showing how to create and manipulate components programmatically using the `ComponentFactory` class. 46 | 47 | ## Usage 48 | To view this example, run the development server using: 49 | 50 | ```bash 51 | pnpm dev 52 | ``` 53 | 54 | Then navigate to the UI Elements example from the home page or go directly to `/examples/ui-elements/`. 55 | 56 | ## Integration 57 | All components shown in this example can be used in your own applications by importing and registering them from the Sans UI library: 58 | 59 | ```javascript 60 | import { registerAllComponents } from 'sans-ui/components/html5/index.js'; 61 | 62 | // Register all components 63 | registerAllComponents(); 64 | ``` 65 | 66 | For programmatic component creation, use the Component Factory: 67 | 68 | ```javascript 69 | import ComponentFactory from 'sans-ui/lib/components/ComponentFactory.js'; 70 | 71 | const button = ComponentFactory.createButton({ 72 | textContent: 'Click Me', 73 | id: 'myButton' 74 | }); 75 | 76 | document.body.appendChild(button.getElement()); 77 | ``` 78 | -------------------------------------------------------------------------------- /packages/ui.deprecated/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui.deprecated/favicon.ico -------------------------------------------------------------------------------- /packages/ui.deprecated/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui.deprecated/favicon.png -------------------------------------------------------------------------------- /packages/ui.deprecated/ios/WebViewViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import WebKit 3 | 4 | /** 5 | * WebViewViewController 6 | * A sample iOS view controller that implements the WKWebView integration 7 | */ 8 | class WebViewViewController: UIViewController { 9 | 10 | private var webView: WKWebView! 11 | private var webViewHandler: WKWebViewHandler! 12 | 13 | var initialUrl: URL = URL(string: "https://example.com")! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | setupWebView() 19 | loadInitialUrl() 20 | } 21 | 22 | private func setupWebView() { 23 | // Create WKWebView configuration 24 | let configuration = WKWebViewConfiguration() 25 | configuration.allowsInlineMediaPlayback = true 26 | configuration.mediaTypesRequiringUserActionForPlayback = [] 27 | 28 | // Create WKWebView 29 | webView = WKWebView(frame: view.bounds, configuration: configuration) 30 | webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 31 | view.addSubview(webView) 32 | 33 | // Create and setup the WKWebView handler 34 | webViewHandler = WKWebViewHandler(webView: webView) 35 | } 36 | 37 | private func loadInitialUrl() { 38 | let request = URLRequest(url: initialUrl) 39 | webView.load(request) 40 | } 41 | 42 | // Example of how to expose native functionality to JavaScript 43 | func exposeNativeFunctions() { 44 | // This could include additional native functionality like camera access, location, etc. 45 | // For example, to add a function that shows a native alert: 46 | let script = """ 47 | window.nativeApp = window.nativeApp || {}; 48 | window.nativeApp.showAlert = function(title, message) { 49 | window.webkit.messageHandlers.webViewBridge.postMessage({ 50 | id: 'alert_' + Date.now(), 51 | action: 'showAlert', 52 | data: { title: title, message: message } 53 | }); 54 | }; 55 | """ 56 | 57 | let userScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: false) 58 | webView.configuration.userContentController.addUserScript(userScript) 59 | } 60 | 61 | // MARK: - Additional Native Functionality 62 | 63 | func showNativeAlert(title: String, message: String) { 64 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 65 | alertController.addAction(UIAlertAction(title: "OK", style: .default)) 66 | present(alertController, animated: true) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/ui.deprecated/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui.deprecated/logo.png -------------------------------------------------------------------------------- /packages/ui.deprecated/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/kde/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | app.setApplicationName("Sans UI KDE WebEngine"); 10 | app.setApplicationVersion("1.0.0"); 11 | 12 | QCommandLineParser parser; 13 | parser.setApplicationDescription("Sans UI KDE WebEngine Bridge"); 14 | parser.addHelpOption(); 15 | parser.addVersionOption(); 16 | 17 | // Add URL option 18 | QCommandLineOption urlOption(QStringList() << "u" << "url", "URL to load", "url", "http://localhost:3000"); 19 | parser.addOption(urlOption); 20 | 21 | // Add title option 22 | QCommandLineOption titleOption(QStringList() << "t" << "title", "Window title", "title", "Sans UI Application"); 23 | parser.addOption(titleOption); 24 | 25 | // Add window size options 26 | QCommandLineOption widthOption(QStringList() << "w" << "width", "Window width", "width", "800"); 27 | QCommandLineOption heightOption(QStringList() << "h" << "height", "Window height", "height", "600"); 28 | parser.addOption(widthOption); 29 | parser.addOption(heightOption); 30 | 31 | // Process the command line arguments 32 | parser.process(app); 33 | 34 | // Get values from command line 35 | QString url = parser.value(urlOption); 36 | QString title = parser.value(titleOption); 37 | int width = parser.value(widthOption).toInt(); 38 | int height = parser.value(heightOption).toInt(); 39 | 40 | // Create and show the main window 41 | MainWindow mainWindow; 42 | mainWindow.setWindowTitle(title); 43 | mainWindow.resize(width, height); 44 | mainWindow.loadUrl(QUrl(url)); 45 | mainWindow.show(); 46 | 47 | return app.exec(); 48 | } 49 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/kde/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "webbridge.h" 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | void loadUrl(const QUrl &url); 19 | 20 | private: 21 | QWebEngineView *m_webView; 22 | WebBridge *m_webBridge; 23 | QWebChannel *m_webChannel; 24 | 25 | void setupWebView(); 26 | void setupWebChannel(); 27 | void setupMenus(); 28 | }; 29 | 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/kde/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/kde/sans-ui-kde.pro: -------------------------------------------------------------------------------- 1 | QT += core gui webengine webenginewidgets 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # You can make your code fail to compile if it uses deprecated APIs. 8 | # In order to do so, uncomment the following line. 9 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 10 | 11 | # KDE integration 12 | CONFIG += link_pkgconfig 13 | PKGCONFIG += knotifications 14 | 15 | SOURCES += \ 16 | main.cpp \ 17 | mainwindow.cpp \ 18 | webbridge.cpp 19 | 20 | HEADERS += \ 21 | mainwindow.h \ 22 | webbridge.h 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | 29 | # Include the qwebchannel.js file 30 | RESOURCES += \ 31 | resources.qrc 32 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/kde/webbridge.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBBRIDGE_H 2 | #define WEBBRIDGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebBridge : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit WebBridge(QObject *parent = nullptr); 16 | 17 | public slots: 18 | // Method to receive messages from JavaScript 19 | void sendMessage(const QString &messageJson); 20 | 21 | // Native functionality exposed to JavaScript 22 | QString openFileDialog(const QVariantMap &options); 23 | bool showNotification(const QVariantMap &options); 24 | 25 | signals: 26 | // Signal to send messages to JavaScript 27 | void messageReceived(const QString &messageJson); 28 | 29 | // Signals for specific events 30 | void fileSelected(const QString &filePath); 31 | 32 | private: 33 | // Helper methods 34 | void sendResponse(const QString &id, const QVariant &data, const QString &error = QString()); 35 | void processMessage(const QJsonObject &message); 36 | }; 37 | 38 | #endif // WEBBRIDGE_H 39 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/qt/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | app.setApplicationName("Sans UI Qt WebEngine"); 10 | app.setApplicationVersion("1.0.0"); 11 | 12 | QCommandLineParser parser; 13 | parser.setApplicationDescription("Sans UI Qt WebEngine Bridge"); 14 | parser.addHelpOption(); 15 | parser.addVersionOption(); 16 | 17 | // Add URL option 18 | QCommandLineOption urlOption(QStringList() << "u" << "url", "URL to load", "url", "http://localhost:3000"); 19 | parser.addOption(urlOption); 20 | 21 | // Add title option 22 | QCommandLineOption titleOption(QStringList() << "t" << "title", "Window title", "title", "Sans UI Application"); 23 | parser.addOption(titleOption); 24 | 25 | // Add window size options 26 | QCommandLineOption widthOption(QStringList() << "w" << "width", "Window width", "width", "800"); 27 | QCommandLineOption heightOption(QStringList() << "h" << "height", "Window height", "height", "600"); 28 | parser.addOption(widthOption); 29 | parser.addOption(heightOption); 30 | 31 | // Process the command line arguments 32 | parser.process(app); 33 | 34 | // Get values from command line 35 | QString url = parser.value(urlOption); 36 | QString title = parser.value(titleOption); 37 | int width = parser.value(widthOption).toInt(); 38 | int height = parser.value(heightOption).toInt(); 39 | 40 | // Create and show the main window 41 | MainWindow mainWindow; 42 | mainWindow.setWindowTitle(title); 43 | mainWindow.resize(width, height); 44 | mainWindow.loadUrl(QUrl(url)); 45 | mainWindow.show(); 46 | 47 | return app.exec(); 48 | } 49 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/qt/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "webbridge.h" 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | void loadUrl(const QUrl &url); 19 | 20 | private: 21 | QWebEngineView *m_webView; 22 | WebBridge *m_webBridge; 23 | QWebChannel *m_webChannel; 24 | 25 | void setupWebView(); 26 | void setupWebChannel(); 27 | void setupMenus(); 28 | }; 29 | 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/qt/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/qt/sans-ui-qt.pro: -------------------------------------------------------------------------------- 1 | QT += core gui webengine webenginewidgets 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # You can make your code fail to compile if it uses deprecated APIs. 8 | # In order to do so, uncomment the following line. 9 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 10 | 11 | # Optional KDE integration 12 | CONFIG += link_pkgconfig 13 | PKGCONFIG += knotifications 14 | 15 | SOURCES += \ 16 | main.cpp \ 17 | mainwindow.cpp \ 18 | webbridge.cpp 19 | 20 | HEADERS += \ 21 | mainwindow.h \ 22 | webbridge.h 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | 29 | # Include the qwebchannel.js file 30 | RESOURCES += \ 31 | resources.qrc 32 | -------------------------------------------------------------------------------- /packages/ui.deprecated/native/qt/webbridge.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBBRIDGE_H 2 | #define WEBBRIDGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebBridge : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit WebBridge(QObject *parent = nullptr); 16 | 17 | public slots: 18 | // Method to receive messages from JavaScript 19 | void sendMessage(const QString &messageJson); 20 | 21 | // Native functionality exposed to JavaScript 22 | QString openFileDialog(const QVariantMap &options); 23 | bool showNotification(const QVariantMap &options); 24 | 25 | signals: 26 | // Signal to send messages to JavaScript 27 | void messageReceived(const QString &messageJson); 28 | 29 | // Signals for specific events 30 | void fileSelected(const QString &filePath); 31 | 32 | private: 33 | // Helper methods 34 | void sendResponse(const QString &id, const QVariant &data, const QString &error = QString()); 35 | void processMessage(const QJsonObject &message); 36 | }; 37 | 38 | #endif // WEBBRIDGE_H 39 | -------------------------------------------------------------------------------- /packages/ui.deprecated/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@profullstackinc/sans-ui", 3 | "version": "1.0.0", 4 | "description": "A cross-platform native UI library which supports iOS, Android, Windows, macOS, and Linux using WebViewJS, Apple's WKWebView API, and KDE's Qt WebEngine. Built with Svelte 4 and web components.", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "module": "dist/index.js", 8 | "svelte": "src/index.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "types": "./dist/index.d.ts", 13 | "svelte": "./src/index.js", 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | }, 17 | "./html5": { 18 | "types": "./dist/html5/index.d.ts", 19 | "svelte": "./src/components/html5/index.js", 20 | "import": "./dist/html5/index.js", 21 | "require": "./dist/html5/index.cjs" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "files": [ 26 | "src", 27 | "dist" 28 | ], 29 | "scripts": { 30 | "dev": "pnpx vite", 31 | "build": "pnpx vite build && pnpm run build:components", 32 | "build:components": "node scripts/build-components.js", 33 | "build:types": "pnpx tsc --emitDeclarationOnly", 34 | "build:with-types": "pnpx vite build && pnpm run build:components && pnpm run build:types", 35 | "preview": "pnpx vite preview", 36 | "prepublishOnly": "pnpm run build", 37 | "test": "pnpx vitest run", 38 | "test:watch": "pnpx vitest", 39 | "lint": "pnpx eslint .", 40 | "format": "pnpx prettier --write .", 41 | "deno:build": "node scripts/build-deno.js", 42 | "publish:npm": "npm publish", 43 | "publish:bun": "bun publish", 44 | "publish:deno": "pnpm run deno:build && deno publish" 45 | }, 46 | "dependencies": { 47 | "@webviewjs/webview": "^0.1.3" 48 | }, 49 | "peerDependencies": { 50 | "svelte": "^4.0.0" 51 | }, 52 | "devDependencies": { 53 | "@sveltejs/package": "^2.2.2", 54 | "@sveltejs/vite-plugin-svelte": "^2.5.3", 55 | "@types/node": "^22.13.10", 56 | "@typescript-eslint/eslint-plugin": "^6.0.0", 57 | "@typescript-eslint/parser": "^6.0.0", 58 | "eslint": "^8.45.0", 59 | "eslint-plugin-svelte": "^2.30.0", 60 | "prettier": "^3.0.0", 61 | "prettier-plugin-svelte": "^3.0.0", 62 | "svelte": "^4.2.19", 63 | "typescript": "^5.8.2", 64 | "vite": "^4.5.9", 65 | "vitest": "^0.34.0" 66 | }, 67 | "keywords": [ 68 | "svelte", 69 | "web-components", 70 | "ui-library", 71 | "cross-platform", 72 | "html5", 73 | "components" 74 | ], 75 | "author": "Profullstack", 76 | "license": "ISC", 77 | "repository": { 78 | "type": "git", 79 | "url": "https://github.com/profullstack/sans-ui.git" 80 | }, 81 | "bugs": { 82 | "url": "https://github.com/profullstack/sans-ui/issues" 83 | }, 84 | "homepage": "https://github.com/profullstack/sans-ui", 85 | "publishConfig": { 86 | "access": "public", 87 | "@profullstack:registry": "https://npm.pkg.github.com" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packages/ui.deprecated/releaase-package.yml: -------------------------------------------------------------------------------- 1 | name: sans-ui 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-gpr: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | permissions: 22 | packages: write 23 | contents: read 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | registry-url: https://npm.pkg.github.com/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/app.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

Svelte 4 WebView with Platform Abstraction

14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/WebViewComponent.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 |
41 | 42 | 43 |
{currentUrl}
44 |
45 | 46 |
47 |
48 | 49 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/A.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/AComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AComponent.js 3 | * A reusable sans-a web component that can be used in any HTML context 4 | */ 5 | 6 | class SansA extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'href', 'target', 'rel', 'download', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.a = this.shadowRoot.querySelector('a'); 19 | this.a.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('a-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const href = this.getAttribute('href') || '#'; 42 | const target = this.getAttribute('target') || ''; 43 | const rel = this.getAttribute('rel') || ''; 44 | const download = this.getAttribute('download') || ''; 45 | const role = this.getAttribute('role') || ''; 46 | const tabindex = this.getAttribute('tabindex') || ''; 47 | const ariaLabel = this.getAttribute('aria-label') || ''; 48 | const dataTestid = this.getAttribute('data-testid') || ''; 49 | 50 | this.shadowRoot.innerHTML = ` 51 | 70 | 83 | 84 | 85 | `; 86 | } 87 | } 88 | 89 | // Register the custom element if we're in a browser environment 90 | if (typeof window !== 'undefined' && window.customElements) { 91 | customElements.define('sans-a', SansA); 92 | } 93 | 94 | export default SansA; 95 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Article.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/ArticleComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ArticleComponent.js 3 | * A reusable sans-article web component that can be used in any HTML context 4 | */ 5 | 6 | class SansArticle extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.article = this.shadowRoot.querySelector('article'); 19 | this.article.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('article-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-article', SansArticle); 74 | } 75 | 76 | export default SansArticle; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Aside.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/AsideComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AsideComponent.js 3 | * A reusable sans-aside web component that can be used in any HTML context 4 | */ 5 | 6 | class SansAside extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.aside = this.shadowRoot.querySelector('aside'); 19 | this.aside.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('aside-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 | 67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-aside', SansAside); 74 | } 75 | 76 | export default SansAside; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Button.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | 22 | 84 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Div.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/DivComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DivComponent.js 3 | * A reusable sans-div web component that can be used in any HTML context 4 | */ 5 | 6 | class SansDiv extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.div = this.shadowRoot.querySelector('div'); 19 | this.div.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('div-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-div', SansDiv); 74 | } 75 | 76 | export default SansDiv; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Footer.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/FooterComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FooterComponent.js 3 | * A reusable sans-footer web component that can be used in any HTML context 4 | */ 5 | 6 | class SansFooter extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.footer = this.shadowRoot.querySelector('footer'); 19 | this.footer.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('footer-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-footer', SansFooter); 74 | } 75 | 76 | export default SansFooter; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/FormComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FormComponent.js 3 | * A reusable sans-form web component that can be used in any HTML context 4 | */ 5 | 6 | class SansForm extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'action', 'method', 'enctype', 'name', 'target', 'novalidate', 'autocomplete', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.form = this.shadowRoot.querySelector('form'); 19 | 20 | // Add event listeners 21 | this.form.addEventListener('submit', (e) => { 22 | // Prevent default form submission 23 | e.preventDefault(); 24 | 25 | // Dispatch a custom event 26 | this.dispatchEvent(new CustomEvent('form-submit', { 27 | bubbles: true, 28 | composed: true, 29 | detail: { 30 | source: this, 31 | formData: new FormData(this.form) 32 | } 33 | })); 34 | }); 35 | } 36 | 37 | attributeChangedCallback(name, oldValue, newValue) { 38 | if (oldValue === newValue) return; 39 | 40 | if (this.shadowRoot) { 41 | this.render(); 42 | } 43 | } 44 | 45 | render() { 46 | const id = this.getAttribute('id') || ''; 47 | const className = this.getAttribute('class') || ''; 48 | const style = this.getAttribute('style') || ''; 49 | const action = this.getAttribute('action') || ''; 50 | const method = this.getAttribute('method') || 'get'; 51 | const enctype = this.getAttribute('enctype') || ''; 52 | const name = this.getAttribute('name') || ''; 53 | const target = this.getAttribute('target') || ''; 54 | const novalidate = this.hasAttribute('novalidate') ? 'novalidate' : ''; 55 | const autocomplete = this.getAttribute('autocomplete') || ''; 56 | const dataTestid = this.getAttribute('data-testid') || ''; 57 | 58 | this.shadowRoot.innerHTML = ` 59 | 68 |
81 | 82 |
83 | `; 84 | } 85 | } 86 | 87 | // Register the custom element if we're in a browser environment 88 | if (typeof window !== 'undefined' && window.customElements) { 89 | customElements.define('sans-form', SansForm); 90 | } 91 | 92 | export default SansForm; 93 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/H1.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |

25 | 26 |

27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/H1Component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * H1Component.js 3 | * A reusable sans-h1 web component that can be used in any HTML context 4 | */ 5 | 6 | class SansH1 extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.h1 = this.shadowRoot.querySelector('h1'); 19 | this.h1.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('h1-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 59 |

68 | 69 |

70 | `; 71 | } 72 | } 73 | 74 | // Register the custom element if we're in a browser environment 75 | if (typeof window !== 'undefined' && window.customElements) { 76 | customElements.define('sans-h1', SansH1); 77 | } 78 | 79 | export default SansH1; 80 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Header.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HeaderComponent.js 3 | * A reusable sans-header web component that can be used in any HTML context 4 | */ 5 | 6 | class SansHeader extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.header = this.shadowRoot.querySelector('header'); 19 | this.header.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('header-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-header', SansHeader); 74 | } 75 | 76 | export default SansHeader; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Img.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/ImgComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ImgComponent.js 3 | * A reusable sans-img web component that can be used in any HTML context 4 | */ 5 | 6 | class SansImg extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'src', 'alt', 'width', 'height', 'loading', 'decoding', 'role', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.img = this.shadowRoot.querySelector('img'); 19 | this.img.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('img-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const src = this.getAttribute('src') || ''; 42 | const alt = this.getAttribute('alt') || ''; 43 | const width = this.getAttribute('width') || ''; 44 | const height = this.getAttribute('height') || ''; 45 | const loading = this.getAttribute('loading') || ''; 46 | const decoding = this.getAttribute('decoding') || ''; 47 | const role = this.getAttribute('role') || ''; 48 | const ariaLabel = this.getAttribute('aria-label') || ''; 49 | const dataTestid = this.getAttribute('data-testid') || ''; 50 | 51 | this.shadowRoot.innerHTML = ` 52 | 62 | 76 | `; 77 | } 78 | } 79 | 80 | // Register the custom element if we're in a browser environment 81 | if (typeof window !== 'undefined' && window.customElements) { 82 | customElements.define('sans-img', SansImg); 83 | } 84 | 85 | export default SansImg; 86 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/LabelComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LabelComponent.js 3 | * A reusable sans-label web component that can be used in any HTML context 4 | */ 5 | 6 | class SansLabel extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'for', 'form', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.label = this.shadowRoot.querySelector('label'); 19 | 20 | // Add event listeners 21 | this.label.addEventListener('click', (e) => { 22 | // Dispatch a custom event 23 | this.dispatchEvent(new CustomEvent('label-click', { 24 | bubbles: true, 25 | composed: true, 26 | detail: { source: this } 27 | })); 28 | }); 29 | } 30 | 31 | attributeChangedCallback(name, oldValue, newValue) { 32 | if (oldValue === newValue) return; 33 | 34 | if (this.shadowRoot) { 35 | this.render(); 36 | } 37 | } 38 | 39 | render() { 40 | const id = this.getAttribute('id') || ''; 41 | const className = this.getAttribute('class') || ''; 42 | const style = this.getAttribute('style') || ''; 43 | const forAttr = this.getAttribute('for') || ''; 44 | const form = this.getAttribute('form') || ''; 45 | const dataTestid = this.getAttribute('data-testid') || ''; 46 | 47 | this.shadowRoot.innerHTML = ` 48 | 61 | 71 | `; 72 | } 73 | } 74 | 75 | // Register the custom element if we're in a browser environment 76 | if (typeof window !== 'undefined' && window.customElements) { 77 | customElements.define('sans-label', SansLabel); 78 | } 79 | 80 | export default SansLabel; 81 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Main.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/MainComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * MainComponent.js 3 | * A reusable sans-main web component that can be used in any HTML context 4 | */ 5 | 6 | class SansMain extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.main = this.shadowRoot.querySelector('main'); 19 | this.main.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('main-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-main', SansMain); 74 | } 75 | 76 | export default SansMain; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Nav.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/NavComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NavComponent.js 3 | * A reusable sans-nav web component that can be used in any HTML context 4 | */ 5 | 6 | class SansNav extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.nav = this.shadowRoot.querySelector('nav'); 19 | this.nav.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('nav-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 | 67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-nav', SansNav); 74 | } 75 | 76 | export default SansNav; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/OptionComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OptionComponent.js 3 | * A reusable sans-option web component that can be used within sans-select 4 | */ 5 | 6 | class SansOption extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'value', 'disabled', 'selected', 'label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (oldValue === newValue) return; 22 | 23 | if (this.shadowRoot) { 24 | this.render(); 25 | } 26 | } 27 | 28 | render() { 29 | const id = this.getAttribute('id') || ''; 30 | const className = this.getAttribute('class') || ''; 31 | const style = this.getAttribute('style') || ''; 32 | const value = this.getAttribute('value') || ''; 33 | const disabled = this.hasAttribute('disabled') ? 'disabled' : ''; 34 | const selected = this.hasAttribute('selected') ? 'selected' : ''; 35 | const label = this.getAttribute('label') || ''; 36 | const dataTestid = this.getAttribute('data-testid') || ''; 37 | 38 | this.shadowRoot.innerHTML = ` 39 | 44 | 56 | `; 57 | } 58 | } 59 | 60 | // Register the custom element if we're in a browser environment 61 | if (typeof window !== 'undefined' && window.customElements) { 62 | customElements.define('sans-option', SansOption); 63 | } 64 | 65 | export default SansOption; 66 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/P.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |

25 | 26 |

27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/PComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PComponent.js 3 | * A reusable sans-p web component that can be used in any HTML context 4 | */ 5 | 6 | class SansP extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.p = this.shadowRoot.querySelector('p'); 19 | this.p.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('p-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 57 |

66 | 67 |

68 | `; 69 | } 70 | } 71 | 72 | // Register the custom element if we're in a browser environment 73 | if (typeof window !== 'undefined' && window.customElements) { 74 | customElements.define('sans-p', SansP); 75 | } 76 | 77 | export default SansP; 78 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/Section.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/components/html5/SectionComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SectionComponent.js 3 | * A reusable sans-section web component that can be used in any HTML context 4 | */ 5 | 6 | class SansSection extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.section = this.shadowRoot.querySelector('section'); 19 | this.section.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('section-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-section', SansSection); 74 | } 75 | 76 | export default SansSection; 77 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI - Main entry point 3 | * A cross-platform UI library with Svelte and Web Components for HTML5 elements 4 | */ 5 | 6 | // Export all HTML5 components 7 | export * from './components/html5/index.js'; 8 | 9 | // Export version information 10 | export const VERSION = '1.0.0'; 11 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/lib/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI Component Abstraction Layer 3 | * 4 | * This module exports the component abstraction layer for Sans UI components, 5 | * making it easy to create and manage components across different platforms. 6 | */ 7 | 8 | import ComponentAdapter from './ComponentAdapter'; 9 | import ComponentFactory from './ComponentFactory'; 10 | 11 | // Export the component abstraction layer 12 | export { 13 | ComponentAdapter, 14 | ComponentFactory 15 | }; 16 | 17 | // Default export for convenience 18 | export default ComponentFactory; 19 | -------------------------------------------------------------------------------- /packages/ui.deprecated/src/lib/webview/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for Qt WebEngine and other platform-specific objects 3 | */ 4 | 5 | // Qt WebEngine types 6 | interface QWebChannelTransport { 7 | send: (message: any) => void; 8 | onmessage: (callback: (event: MessageEvent) => void) => void; 9 | } 10 | 11 | interface QtObject { 12 | webChannelTransport: QWebChannelTransport; 13 | } 14 | 15 | interface QWebChannelObject { 16 | [key: string]: any; 17 | } 18 | 19 | declare class QWebChannel { 20 | constructor(transport: QWebChannelTransport, callback: (channel: { objects: QWebChannelObject }) => void); 21 | } 22 | 23 | // Extend Window interface to include platform-specific objects 24 | declare interface Window { 25 | // Qt WebEngine specific 26 | qt?: QtObject; 27 | QWebChannel?: typeof QWebChannel; 28 | bridge?: any; 29 | bridgeReady?: (bridge: any) => void; 30 | 31 | // iOS WKWebView specific 32 | webkit?: { 33 | messageHandlers: { 34 | [key: string]: { 35 | postMessage: (message: any) => void; 36 | }; 37 | }; 38 | }; 39 | receiveNativeMessage?: (messageJson: string) => void; 40 | 41 | // Microsoft WebView2 specific 42 | chrome?: any; 43 | MSStream?: any; 44 | } 45 | 46 | // Declare the WebViewJS module 47 | declare module '@webviewjs/webview' { 48 | export default class WebViewJS { 49 | constructor(url: string, options?: any); 50 | element: HTMLElement; 51 | navigate(url: string): void; 52 | reload(): void; 53 | executeJavaScript(code: string): Promise; 54 | destroy(): void; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/api/favicon.sans-api.svg: -------------------------------------------------------------------------------- 1 | API{{ -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/api/favicon.sans-api.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/api/logo.sans-api.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/api/logo.sans-api.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/db/favicon.db.svg: -------------------------------------------------------------------------------- 1 | DB; -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/db/favicon.db.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/db/logo.sans-db.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/ui/favicon.sans-ui.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/static/logos/ui/favicon.sans-ui.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui.deprecated/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "declarationDir": "./dist", 8 | "emitDeclarationOnly": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "types": ["svelte", "node"], 18 | "outDir": "./dist" 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules", "dist"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/ui.deprecated/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | export default defineConfig({ 5 | plugins: [svelte()], 6 | server: { 7 | port: 5173 8 | } 9 | }); -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Environment variables 5 | .env* 6 | 7 | # Build artifacts 8 | /dist/ 9 | /.svelte-kit/ 10 | /build/ 11 | 12 | # IDE and editor files 13 | .idea/ 14 | .vscode/ 15 | *.sublime-project 16 | *.sublime-workspace 17 | 18 | # OS files 19 | .DS_Store 20 | Thumbs.db 21 | 22 | # Logs 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | pnpm-debug.log* 27 | 28 | # Cache 29 | .cache/ 30 | .pnpm-store/ 31 | -------------------------------------------------------------------------------- /packages/ui/bunfig.toml: -------------------------------------------------------------------------------- 1 | # Sans UI Bun Configuration 2 | 3 | [install] 4 | dry-run = false 5 | frozen-lockfile = false 6 | 7 | [install.cache] 8 | dir = ".bun/install/cache" 9 | enabled = true 10 | 11 | [test] 12 | coverage = false 13 | 14 | [build] 15 | entrypoints = ["src/index.js"] 16 | outdir = "dist" 17 | splitting = true 18 | minify = true 19 | modules = true 20 | 21 | [publish] 22 | access = "public" 23 | scope = "profullstack" 24 | registry = "https://registry.npmjs.org/" 25 | -------------------------------------------------------------------------------- /packages/ui/examples/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sans-ui-backend-example", 3 | "version": "1.0.0", 4 | "description": "Backend API example for Sans UI using Elysia and Mailgun", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js", 9 | "dev": "node --watch index.js" 10 | }, 11 | "keywords": [ 12 | "sans-ui", 13 | "elysia", 14 | "mailgun", 15 | "api" 16 | ], 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@elysiajs/cors": "^0.8.0", 21 | "@supabase/supabase-js": "^2.39.0", 22 | "dotenv": "^16.3.1", 23 | "elysia": "^0.8.9", 24 | "form-data": "^4.0.0", 25 | "mailgun.js": "^9.3.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui/examples/backend/sample.env: -------------------------------------------------------------------------------- 1 | # API Configuration 2 | PORT=3001 3 | API_KEY=your_api_key_here 4 | 5 | # Mailgun Configuration 6 | MAILGUN_API_KEY=your_mailgun_api_key_here 7 | MAILGUN_DOMAIN=your_mailgun_domain.com 8 | EMAIL_FROM=Sans UI 9 | 10 | # Supabase Configuration 11 | SUPABASE_URL=https://your-project-id.supabase.co 12 | SUPABASE_KEY=your_supabase_anon_key_here 13 | SUPABASE_TABLE=subscribers 14 | 15 | # CORS Configuration 16 | ALLOW_ORIGINS=http://localhost:3000,http://localhost:8000 17 | -------------------------------------------------------------------------------- /packages/ui/examples/camera-subscribe-example/css/styles.css: -------------------------------------------------------------------------------- 1 | /* Camera & Subscribe Example Styles */ 2 | 3 | .camera-container { 4 | margin: 20px 0; 5 | padding: 20px; 6 | background-color: #f9f9f9; 7 | border-radius: 8px; 8 | border: 1px solid var(--border-color); 9 | } 10 | 11 | #camera-placeholder { 12 | width: 100%; 13 | height: 300px; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | background-color: #eee; 18 | border-radius: 4px; 19 | color: #666; 20 | font-style: italic; 21 | } 22 | 23 | #video { 24 | width: 100%; 25 | max-width: 640px; 26 | height: auto; 27 | border-radius: 4px; 28 | background-color: #000; 29 | } 30 | 31 | .camera-controls { 32 | margin-top: 15px; 33 | display: flex; 34 | gap: 10px; 35 | } 36 | 37 | #photo-container { 38 | margin-top: 20px; 39 | padding: 15px; 40 | background-color: white; 41 | border-radius: 4px; 42 | border: 1px solid var(--border-color); 43 | } 44 | 45 | #photo { 46 | max-width: 100%; 47 | border-radius: 4px; 48 | display: block; 49 | margin: 10px 0; 50 | } 51 | 52 | .subscribe-container { 53 | margin: 20px 0; 54 | padding: 20px; 55 | background-color: white; 56 | border-radius: 8px; 57 | border: 1px solid var(--border-color); 58 | max-width: 500px; 59 | } 60 | 61 | .form-group { 62 | margin-bottom: 15px; 63 | } 64 | 65 | label { 66 | display: block; 67 | margin-bottom: 5px; 68 | font-weight: 500; 69 | } 70 | 71 | input[type="email"], 72 | input[type="text"] { 73 | width: 100%; 74 | padding: 10px; 75 | border: 1px solid var(--border-color); 76 | border-radius: 4px; 77 | font-size: 16px; 78 | } 79 | 80 | input[type="checkbox"] { 81 | margin-right: 8px; 82 | } 83 | 84 | .form-actions { 85 | margin-top: 20px; 86 | } 87 | 88 | /* Button styles */ 89 | .btn { 90 | display: inline-block; 91 | padding: 8px 16px; 92 | border: none; 93 | border-radius: 4px; 94 | font-size: 16px; 95 | font-weight: 500; 96 | cursor: pointer; 97 | transition: background-color 0.2s, transform 0.1s; 98 | } 99 | 100 | .btn:hover:not(:disabled) { 101 | filter: brightness(1.1); 102 | } 103 | 104 | .btn:active:not(:disabled) { 105 | transform: scale(0.98); 106 | } 107 | 108 | .btn:disabled { 109 | opacity: 0.6; 110 | cursor: not-allowed; 111 | } 112 | 113 | .btn.primary { 114 | background-color: var(--primary-color); 115 | color: white; 116 | } 117 | 118 | .btn.secondary { 119 | background-color: var(--secondary-color); 120 | color: var(--text-color); 121 | } 122 | 123 | .btn.danger { 124 | background-color: var(--danger-color); 125 | color: white; 126 | } 127 | -------------------------------------------------------------------------------- /packages/ui/examples/camera-subscribe-example/js/subscribe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Subscribe Module 3 | * Handles subscription form submission and validation 4 | */ 5 | 6 | /** 7 | * Handle subscription form submission 8 | * @param {Object} data - Subscription data 9 | * @param {string} data.email - Email address 10 | * @param {string} data.name - Full name 11 | * @param {boolean} data.terms - Terms acceptance 12 | * @returns {Promise} - Subscription result 13 | */ 14 | export async function handleSubscribe(data) { 15 | // Validate data 16 | if (!data.email) { 17 | throw new Error('Email is required'); 18 | } 19 | 20 | if (!isValidEmail(data.email)) { 21 | throw new Error('Invalid email format'); 22 | } 23 | 24 | if (!data.name) { 25 | throw new Error('Name is required'); 26 | } 27 | 28 | if (!data.terms) { 29 | throw new Error('You must accept the terms and conditions'); 30 | } 31 | 32 | // Simulate API call 33 | return new Promise((resolve) => { 34 | setTimeout(() => { 35 | resolve({ 36 | success: true, 37 | message: `Thank you, ${data.name}! You have been subscribed with email: ${data.email}`, 38 | data 39 | }); 40 | }, 1000); 41 | }); 42 | } 43 | 44 | /** 45 | * Validate email format 46 | * @param {string} email - Email to validate 47 | * @returns {boolean} - True if email is valid 48 | */ 49 | function isValidEmail(email) { 50 | const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 51 | return emailRegex.test(email); 52 | } 53 | 54 | /** 55 | * Sync with backend service 56 | * This is a placeholder for actual backend synchronization 57 | * @param {Object} data - Subscription data 58 | * @returns {Promise} - Sync result 59 | */ 60 | export async function syncWithService(data) { 61 | // This would normally make an API call to a backend service 62 | console.log('Syncing with service:', data); 63 | 64 | // Simulate API call 65 | return new Promise((resolve) => { 66 | setTimeout(() => { 67 | resolve({ 68 | success: true, 69 | message: 'Data synced successfully', 70 | timestamp: new Date().toISOString() 71 | }); 72 | }, 500); 73 | }); 74 | } 75 | 76 | // Export all functions 77 | export default { 78 | handleSubscribe, 79 | syncWithService 80 | }; 81 | -------------------------------------------------------------------------------- /packages/ui/examples/common/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI Examples - Common JavaScript 3 | * Provides common functionality for all examples 4 | */ 5 | 6 | // Import Sans UI 7 | import { 8 | initialize, 9 | getPlatformInfo 10 | } from '../../src/index.js'; 11 | 12 | // Initialize Sans UI 13 | initialize(); 14 | 15 | /** 16 | * Set the active navigation link based on the current page 17 | */ 18 | export function setupNavigation() { 19 | const currentPath = window.location.pathname; 20 | const navLinks = document.querySelectorAll('.nav-link'); 21 | 22 | navLinks.forEach(link => { 23 | const linkPath = link.getAttribute('href'); 24 | if (currentPath.endsWith(linkPath)) { 25 | link.classList.add('active'); 26 | } 27 | }); 28 | } 29 | 30 | /** 31 | * Display platform information in the specified element 32 | * @param {string} elementId - The ID of the element to display platform info in 33 | */ 34 | export function displayPlatformInfo(elementId = 'platform-output') { 35 | const platformInfo = getPlatformInfo(); 36 | const element = document.getElementById(elementId); 37 | 38 | if (element) { 39 | element.textContent = JSON.stringify(platformInfo, null, 2); 40 | } 41 | } 42 | 43 | /** 44 | * Add syntax highlighting to code blocks 45 | * This is a simple implementation - in a real app you might use a library like Prism.js 46 | */ 47 | export function highlightCode() { 48 | const codeBlocks = document.querySelectorAll('pre code'); 49 | 50 | codeBlocks.forEach(block => { 51 | // Simple syntax highlighting for HTML 52 | const html = block.innerHTML; 53 | const highlighted = html 54 | .replace(/<(\/?[a-zA-Z0-9-]+)>/g, '<$1>') 55 | .replace(/<([a-zA-Z0-9-]+)(\s+)/g, '<$1$2') 56 | .replace(/(\s+)([a-zA-Z0-9-]+)=/g, '$1$2=') 57 | .replace(/="([^&]*)"/g, '="$1"'); 58 | 59 | block.innerHTML = highlighted; 60 | }); 61 | } 62 | 63 | /** 64 | * Initialize the example page 65 | */ 66 | export function initExample() { 67 | // Set up navigation 68 | setupNavigation(); 69 | 70 | // Display platform information 71 | displayPlatformInfo(); 72 | 73 | // Add syntax highlighting 74 | highlightCode(); 75 | 76 | // Log initialization 77 | console.log('Sans UI Example initialized'); 78 | } 79 | 80 | // Export all common functions 81 | export default { 82 | setupNavigation, 83 | displayPlatformInfo, 84 | highlightCode, 85 | initExample 86 | }; -------------------------------------------------------------------------------- /packages/ui/examples/common/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/ui/examples/ui-elements/README.md: -------------------------------------------------------------------------------- 1 |

2 | Sans UI Logo 3 | Sans API Logo 4 | Sans DB Logo 5 |

6 | 7 | # Sans UI Elements Showcase 8 | 9 | This example showcases all the available UI elements and components in the Sans UI library. It serves as a visual reference and documentation for developers using the Sans UI framework. 10 | 11 | ## Components Demonstrated 12 | 13 | ### Basic HTML Components 14 | - `sans-div`: Basic container component 15 | - `sans-p`: Paragraph component 16 | - `sans-h1`: Heading component 17 | - `sans-a`: Anchor/link component 18 | - `sans-img`: Image component 19 | - `sans-button`: Button component 20 | 21 | ### Form Components 22 | - `sans-form`: Form container component 23 | - `sans-input`: Input field component (text, number, checkbox, etc.) 24 | - `sans-select`: Dropdown select component 25 | - `sans-option`: Option component for select dropdowns 26 | - `sans-textarea`: Multi-line text input component 27 | - `sans-label`: Label component for form fields 28 | 29 | ### Layout Components 30 | - `sans-section`: Section container component 31 | - `sans-article`: Article container component 32 | - `sans-header`: Header component 33 | - `sans-footer`: Footer component 34 | 35 | ## Color Palette 36 | The example also demonstrates the standard color palette used in Sans UI: 37 | - Primary: #6200ee 38 | - Secondary: #03dac6 39 | - Error: #b00020 40 | - Warning: #ffab00 41 | - Info: #2196f3 42 | - Success: #4caf50 43 | 44 | ## Component Abstraction Layer 45 | The example includes a demonstration of the Component Abstraction Layer, showing how to create and manipulate components programmatically using the `ComponentFactory` class. 46 | 47 | ## Usage 48 | To view this example, run the development server using: 49 | 50 | ```bash 51 | pnpm dev 52 | ``` 53 | 54 | Then navigate to the UI Elements example from the home page or go directly to `/examples/ui-elements/`. 55 | 56 | ## Integration 57 | All components shown in this example can be used in your own applications by importing and registering them from the Sans UI library: 58 | 59 | ```javascript 60 | import { registerAllComponents } from 'sans-ui/components/html5/index.js'; 61 | 62 | // Register all components 63 | registerAllComponents(); 64 | ``` 65 | 66 | For programmatic component creation, use the Component Factory: 67 | 68 | ```javascript 69 | import ComponentFactory from 'sans-ui/lib/components/ComponentFactory.js'; 70 | 71 | const button = ComponentFactory.createButton({ 72 | textContent: 'Click Me', 73 | id: 'myButton' 74 | }); 75 | 76 | document.body.appendChild(button.getElement()); 77 | ``` 78 | -------------------------------------------------------------------------------- /packages/ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui/favicon.ico -------------------------------------------------------------------------------- /packages/ui/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui/favicon.png -------------------------------------------------------------------------------- /packages/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sans UI - Native UI Architecture 7 | 8 | 16 | 17 | 18 |
19 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/ui/ios/WebViewViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import WebKit 3 | 4 | /** 5 | * WebViewViewController 6 | * A sample iOS view controller that implements the WKWebView integration 7 | */ 8 | class WebViewViewController: UIViewController { 9 | 10 | private var webView: WKWebView! 11 | private var webViewHandler: WKWebViewHandler! 12 | 13 | var initialUrl: URL = URL(string: "https://example.com")! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | setupWebView() 19 | loadInitialUrl() 20 | } 21 | 22 | private func setupWebView() { 23 | // Create WKWebView configuration 24 | let configuration = WKWebViewConfiguration() 25 | configuration.allowsInlineMediaPlayback = true 26 | configuration.mediaTypesRequiringUserActionForPlayback = [] 27 | 28 | // Create WKWebView 29 | webView = WKWebView(frame: view.bounds, configuration: configuration) 30 | webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 31 | view.addSubview(webView) 32 | 33 | // Create and setup the WKWebView handler 34 | webViewHandler = WKWebViewHandler(webView: webView) 35 | } 36 | 37 | private func loadInitialUrl() { 38 | let request = URLRequest(url: initialUrl) 39 | webView.load(request) 40 | } 41 | 42 | // Example of how to expose native functionality to JavaScript 43 | func exposeNativeFunctions() { 44 | // This could include additional native functionality like camera access, location, etc. 45 | // For example, to add a function that shows a native alert: 46 | let script = """ 47 | window.nativeApp = window.nativeApp || {}; 48 | window.nativeApp.showAlert = function(title, message) { 49 | window.webkit.messageHandlers.webViewBridge.postMessage({ 50 | id: 'alert_' + Date.now(), 51 | action: 'showAlert', 52 | data: { title: title, message: message } 53 | }); 54 | }; 55 | """ 56 | 57 | let userScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: false) 58 | webView.configuration.userContentController.addUserScript(userScript) 59 | } 60 | 61 | // MARK: - Additional Native Functionality 62 | 63 | func showNativeAlert(title: String, message: String) { 64 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 65 | alertController.addAction(UIAlertAction(title: "OK", style: .default)) 66 | present(alertController, animated: true) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/ui/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profullstack/sans/2f6b03811f6ee84704ecad6d7b72317c55c90709/packages/ui/logo.png -------------------------------------------------------------------------------- /packages/ui/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/ui/native/kde/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | app.setApplicationName("Sans UI KDE WebEngine"); 10 | app.setApplicationVersion("1.0.0"); 11 | 12 | QCommandLineParser parser; 13 | parser.setApplicationDescription("Sans UI KDE WebEngine Bridge"); 14 | parser.addHelpOption(); 15 | parser.addVersionOption(); 16 | 17 | // Add URL option 18 | QCommandLineOption urlOption(QStringList() << "u" << "url", "URL to load", "url", "http://localhost:3000"); 19 | parser.addOption(urlOption); 20 | 21 | // Add title option 22 | QCommandLineOption titleOption(QStringList() << "t" << "title", "Window title", "title", "Sans UI Application"); 23 | parser.addOption(titleOption); 24 | 25 | // Add window size options 26 | QCommandLineOption widthOption(QStringList() << "w" << "width", "Window width", "width", "800"); 27 | QCommandLineOption heightOption(QStringList() << "h" << "height", "Window height", "height", "600"); 28 | parser.addOption(widthOption); 29 | parser.addOption(heightOption); 30 | 31 | // Process the command line arguments 32 | parser.process(app); 33 | 34 | // Get values from command line 35 | QString url = parser.value(urlOption); 36 | QString title = parser.value(titleOption); 37 | int width = parser.value(widthOption).toInt(); 38 | int height = parser.value(heightOption).toInt(); 39 | 40 | // Create and show the main window 41 | MainWindow mainWindow; 42 | mainWindow.setWindowTitle(title); 43 | mainWindow.resize(width, height); 44 | mainWindow.loadUrl(QUrl(url)); 45 | mainWindow.show(); 46 | 47 | return app.exec(); 48 | } 49 | -------------------------------------------------------------------------------- /packages/ui/native/kde/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "webbridge.h" 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | void loadUrl(const QUrl &url); 19 | 20 | private: 21 | QWebEngineView *m_webView; 22 | WebBridge *m_webBridge; 23 | QWebChannel *m_webChannel; 24 | 25 | void setupWebView(); 26 | void setupWebChannel(); 27 | void setupMenus(); 28 | }; 29 | 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /packages/ui/native/kde/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ui/native/kde/sans-ui-kde.pro: -------------------------------------------------------------------------------- 1 | QT += core gui webengine webenginewidgets 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # You can make your code fail to compile if it uses deprecated APIs. 8 | # In order to do so, uncomment the following line. 9 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 10 | 11 | # KDE integration 12 | CONFIG += link_pkgconfig 13 | PKGCONFIG += knotifications 14 | 15 | SOURCES += \ 16 | main.cpp \ 17 | mainwindow.cpp \ 18 | webbridge.cpp 19 | 20 | HEADERS += \ 21 | mainwindow.h \ 22 | webbridge.h 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | 29 | # Include the qwebchannel.js file 30 | RESOURCES += \ 31 | resources.qrc 32 | -------------------------------------------------------------------------------- /packages/ui/native/kde/webbridge.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBBRIDGE_H 2 | #define WEBBRIDGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebBridge : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit WebBridge(QObject *parent = nullptr); 16 | 17 | public slots: 18 | // Method to receive messages from JavaScript 19 | void sendMessage(const QString &messageJson); 20 | 21 | // Native functionality exposed to JavaScript 22 | QString openFileDialog(const QVariantMap &options); 23 | bool showNotification(const QVariantMap &options); 24 | 25 | signals: 26 | // Signal to send messages to JavaScript 27 | void messageReceived(const QString &messageJson); 28 | 29 | // Signals for specific events 30 | void fileSelected(const QString &filePath); 31 | 32 | private: 33 | // Helper methods 34 | void sendResponse(const QString &id, const QVariant &data, const QString &error = QString()); 35 | void processMessage(const QJsonObject &message); 36 | }; 37 | 38 | #endif // WEBBRIDGE_H 39 | -------------------------------------------------------------------------------- /packages/ui/native/qt/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "mainwindow.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication app(argc, argv); 9 | app.setApplicationName("Sans UI Qt WebEngine"); 10 | app.setApplicationVersion("1.0.0"); 11 | 12 | QCommandLineParser parser; 13 | parser.setApplicationDescription("Sans UI Qt WebEngine Bridge"); 14 | parser.addHelpOption(); 15 | parser.addVersionOption(); 16 | 17 | // Add URL option 18 | QCommandLineOption urlOption(QStringList() << "u" << "url", "URL to load", "url", "http://localhost:3000"); 19 | parser.addOption(urlOption); 20 | 21 | // Add title option 22 | QCommandLineOption titleOption(QStringList() << "t" << "title", "Window title", "title", "Sans UI Application"); 23 | parser.addOption(titleOption); 24 | 25 | // Add window size options 26 | QCommandLineOption widthOption(QStringList() << "w" << "width", "Window width", "width", "800"); 27 | QCommandLineOption heightOption(QStringList() << "h" << "height", "Window height", "height", "600"); 28 | parser.addOption(widthOption); 29 | parser.addOption(heightOption); 30 | 31 | // Process the command line arguments 32 | parser.process(app); 33 | 34 | // Get values from command line 35 | QString url = parser.value(urlOption); 36 | QString title = parser.value(titleOption); 37 | int width = parser.value(widthOption).toInt(); 38 | int height = parser.value(heightOption).toInt(); 39 | 40 | // Create and show the main window 41 | MainWindow mainWindow; 42 | mainWindow.setWindowTitle(title); 43 | mainWindow.resize(width, height); 44 | mainWindow.loadUrl(QUrl(url)); 45 | mainWindow.show(); 46 | 47 | return app.exec(); 48 | } 49 | -------------------------------------------------------------------------------- /packages/ui/native/qt/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "webbridge.h" 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | void loadUrl(const QUrl &url); 19 | 20 | private: 21 | QWebEngineView *m_webView; 22 | WebBridge *m_webBridge; 23 | QWebChannel *m_webChannel; 24 | 25 | void setupWebView(); 26 | void setupWebChannel(); 27 | void setupMenus(); 28 | }; 29 | 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /packages/ui/native/qt/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/ui/native/qt/sans-ui-qt.pro: -------------------------------------------------------------------------------- 1 | QT += core gui webengine webenginewidgets 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # You can make your code fail to compile if it uses deprecated APIs. 8 | # In order to do so, uncomment the following line. 9 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 10 | 11 | # Optional KDE integration 12 | CONFIG += link_pkgconfig 13 | PKGCONFIG += knotifications 14 | 15 | SOURCES += \ 16 | main.cpp \ 17 | mainwindow.cpp \ 18 | webbridge.cpp 19 | 20 | HEADERS += \ 21 | mainwindow.h \ 22 | webbridge.h 23 | 24 | # Default rules for deployment. 25 | qnx: target.path = /tmp/$${TARGET}/bin 26 | else: unix:!android: target.path = /opt/$${TARGET}/bin 27 | !isEmpty(target.path): INSTALLS += target 28 | 29 | # Include the qwebchannel.js file 30 | RESOURCES += \ 31 | resources.qrc 32 | -------------------------------------------------------------------------------- /packages/ui/native/qt/webbridge.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBBRIDGE_H 2 | #define WEBBRIDGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebBridge : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit WebBridge(QObject *parent = nullptr); 16 | 17 | public slots: 18 | // Method to receive messages from JavaScript 19 | void sendMessage(const QString &messageJson); 20 | 21 | // Native functionality exposed to JavaScript 22 | QString openFileDialog(const QVariantMap &options); 23 | bool showNotification(const QVariantMap &options); 24 | 25 | signals: 26 | // Signal to send messages to JavaScript 27 | void messageReceived(const QString &messageJson); 28 | 29 | // Signals for specific events 30 | void fileSelected(const QString &filePath); 31 | 32 | private: 33 | // Helper methods 34 | void sendResponse(const QString &id, const QVariant &data, const QString &error = QString()); 35 | void processMessage(const QJsonObject &message); 36 | }; 37 | 38 | #endif // WEBBRIDGE_H 39 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@profullstack/sans-ui", 3 | "version": "1.0.3", 4 | "description": "A cross-platform native UI library which supports iOS, Android, Windows, macOS, and Linux using NodeGUI for desktop and NativeScript for mobile. Built with Svelte 4 and web components.", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "module": "dist/index.js", 8 | "svelte": "src/index.js", 9 | "types": "dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "types": "./dist/index.d.ts", 13 | "svelte": "./src/index.js", 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs" 16 | }, 17 | "./html5": { 18 | "types": "./dist/html5/index.d.ts", 19 | "svelte": "./src/components/html5/index.js", 20 | "import": "./dist/html5/index.js", 21 | "require": "./dist/html5/index.cjs" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "files": [ 26 | "src", 27 | "dist" 28 | ], 29 | "scripts": { 30 | "dev": "pnpx vite", 31 | "docs": "pnpx vite --config vite.docs.js", 32 | "docs:build": "pnpx vite build --config vite.docs.js", 33 | "build": "pnpx vite build && pnpm run build:components", 34 | "build:components": "node scripts/build-components.js", 35 | "build:types": "pnpx tsc --emitDeclarationOnly", 36 | "build:with-types": "pnpx vite build && pnpm run build:components && pnpm run build:types", 37 | "preview": "pnpx vite preview", 38 | "prepublishOnly": "pnpm run build", 39 | "test": "pnpx vitest run", 40 | "test:watch": "pnpx vitest", 41 | "lint": "pnpx eslint .", 42 | "format": "pnpx prettier --write .", 43 | "deno:build": "node scripts/build-deno.js", 44 | "publish:npm": "npm publish", 45 | "publish:bun": "bun publish", 46 | "publish:deno": "pnpm run deno:build && deno publish", 47 | "desktop": "node scripts/run-desktop.js", 48 | "mobile": "node scripts/run-mobile.js" 49 | }, 50 | "dependencies": { 51 | "@nodegui/nodegui": "^0.57.0", 52 | "@nativescript/core": "^8.5.0", 53 | "yoga-layout": "^2.0.0" 54 | }, 55 | "peerDependencies": { 56 | "svelte": "^4.0.0" 57 | }, 58 | "devDependencies": { 59 | "@sveltejs/package": "^2.2.2", 60 | "@sveltejs/vite-plugin-svelte": "^2.5.3", 61 | "@types/node": "^22.13.10", 62 | "@typescript-eslint/eslint-plugin": "^6.0.0", 63 | "@typescript-eslint/parser": "^6.0.0", 64 | "eslint": "^8.45.0", 65 | "eslint-plugin-svelte": "^2.30.0", 66 | "prettier": "^3.0.0", 67 | "prettier-plugin-svelte": "^3.0.0", 68 | "svelte": "^4.2.19", 69 | "typescript": "^5.8.2", 70 | "vite": "^4.5.9", 71 | "vitest": "^0.34.0" 72 | }, 73 | "keywords": [ 74 | "svelte", 75 | "web-components", 76 | "ui-library", 77 | "cross-platform", 78 | "html5", 79 | "components", 80 | "nodegui", 81 | "nativescript", 82 | "native-ui" 83 | ], 84 | "author": "Profullstack", 85 | "license": "ISC", 86 | "repository": { 87 | "type": "git", 88 | "url": "https://github.com/profullstack/sans-ui.git" 89 | }, 90 | "bugs": { 91 | "url": "https://github.com/profullstack/sans-ui/issues" 92 | }, 93 | "homepage": "https://github.com/profullstack/sans-ui", 94 | "publishConfig": { 95 | "access": "public" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/ui/releaase-package.yml: -------------------------------------------------------------------------------- 1 | name: sans-ui 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-gpr: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | permissions: 22 | packages: write 23 | contents: read 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | registry-url: https://npm.pkg.github.com/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | -------------------------------------------------------------------------------- /packages/ui/src/app.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 |

Sans UI - Native UI Architecture

20 | 21 |
22 |

Platform Information

23 |
{JSON.stringify(platformInfo, null, 2)}
24 |
25 | 26 |
27 |

Button Component Example

28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 |
37 |

Architecture Overview

38 |

39 | This demo showcases the new Sans UI architecture that uses: 40 |

41 |
    42 |
  • NodeGUI for desktop platforms (Windows, macOS, Linux)
  • 43 |
  • NativeScript for mobile platforms (iOS, Android)
  • 44 |
  • Web Components and Svelte for the developer API
  • 45 |
46 |

47 | The architecture automatically detects the current platform and uses the appropriate native UI renderer. 48 |

49 |
50 |
51 | 52 | -------------------------------------------------------------------------------- /packages/ui/src/components/WebViewComponent.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 |
41 | 42 | 43 |
{currentUrl}
44 |
45 | 46 |
47 |
48 | 49 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/A.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/AComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AComponent.js 3 | * A reusable sans-a web component that can be used in any HTML context 4 | */ 5 | 6 | class SansA extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'href', 'target', 'rel', 'download', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.a = this.shadowRoot.querySelector('a'); 19 | this.a.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('a-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const href = this.getAttribute('href') || '#'; 42 | const target = this.getAttribute('target') || ''; 43 | const rel = this.getAttribute('rel') || ''; 44 | const download = this.getAttribute('download') || ''; 45 | const role = this.getAttribute('role') || ''; 46 | const tabindex = this.getAttribute('tabindex') || ''; 47 | const ariaLabel = this.getAttribute('aria-label') || ''; 48 | const dataTestid = this.getAttribute('data-testid') || ''; 49 | 50 | this.shadowRoot.innerHTML = ` 51 | 70 | 83 | 84 | 85 | `; 86 | } 87 | } 88 | 89 | // Register the custom element if we're in a browser environment 90 | if (typeof window !== 'undefined' && window.customElements) { 91 | customElements.define('sans-a', SansA); 92 | } 93 | 94 | export default SansA; 95 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Article.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/ArticleComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ArticleComponent.js 3 | * A reusable sans-article web component that can be used in any HTML context 4 | */ 5 | 6 | class SansArticle extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.article = this.shadowRoot.querySelector('article'); 19 | this.article.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('article-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-article', SansArticle); 74 | } 75 | 76 | export default SansArticle; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Aside.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/AsideComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AsideComponent.js 3 | * A reusable sans-aside web component that can be used in any HTML context 4 | */ 5 | 6 | class SansAside extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.aside = this.shadowRoot.querySelector('aside'); 19 | this.aside.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('aside-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 | 67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-aside', SansAside); 74 | } 75 | 76 | export default SansAside; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Div.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/DivComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DivComponent.js 3 | * A reusable sans-div web component that can be used in any HTML context 4 | */ 5 | 6 | class SansDiv extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.div = this.shadowRoot.querySelector('div'); 19 | this.div.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('div-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-div', SansDiv); 74 | } 75 | 76 | export default SansDiv; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Footer.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/FooterComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FooterComponent.js 3 | * A reusable sans-footer web component that can be used in any HTML context 4 | */ 5 | 6 | class SansFooter extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.footer = this.shadowRoot.querySelector('footer'); 19 | this.footer.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('footer-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-footer', SansFooter); 74 | } 75 | 76 | export default SansFooter; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/FormComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FormComponent.js 3 | * A reusable sans-form web component that can be used in any HTML context 4 | */ 5 | 6 | class SansForm extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'action', 'method', 'enctype', 'name', 'target', 'novalidate', 'autocomplete', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.form = this.shadowRoot.querySelector('form'); 19 | 20 | // Add event listeners 21 | this.form.addEventListener('submit', (e) => { 22 | // Prevent default form submission 23 | e.preventDefault(); 24 | 25 | // Dispatch a custom event 26 | this.dispatchEvent(new CustomEvent('form-submit', { 27 | bubbles: true, 28 | composed: true, 29 | detail: { 30 | source: this, 31 | formData: new FormData(this.form) 32 | } 33 | })); 34 | }); 35 | } 36 | 37 | attributeChangedCallback(name, oldValue, newValue) { 38 | if (oldValue === newValue) return; 39 | 40 | if (this.shadowRoot) { 41 | this.render(); 42 | } 43 | } 44 | 45 | render() { 46 | const id = this.getAttribute('id') || ''; 47 | const className = this.getAttribute('class') || ''; 48 | const style = this.getAttribute('style') || ''; 49 | const action = this.getAttribute('action') || ''; 50 | const method = this.getAttribute('method') || 'get'; 51 | const enctype = this.getAttribute('enctype') || ''; 52 | const name = this.getAttribute('name') || ''; 53 | const target = this.getAttribute('target') || ''; 54 | const novalidate = this.hasAttribute('novalidate') ? 'novalidate' : ''; 55 | const autocomplete = this.getAttribute('autocomplete') || ''; 56 | const dataTestid = this.getAttribute('data-testid') || ''; 57 | 58 | this.shadowRoot.innerHTML = ` 59 | 68 |
81 | 82 |
83 | `; 84 | } 85 | } 86 | 87 | // Register the custom element if we're in a browser environment 88 | if (typeof window !== 'undefined' && window.customElements) { 89 | customElements.define('sans-form', SansForm); 90 | } 91 | 92 | export default SansForm; 93 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/H1.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |

25 | 26 |

27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/H1Component.js: -------------------------------------------------------------------------------- 1 | /** 2 | * H1Component.js 3 | * A reusable sans-h1 web component that can be used in any HTML context 4 | */ 5 | 6 | class SansH1 extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.h1 = this.shadowRoot.querySelector('h1'); 19 | this.h1.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('h1-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 59 |

68 | 69 |

70 | `; 71 | } 72 | } 73 | 74 | // Register the custom element if we're in a browser environment 75 | if (typeof window !== 'undefined' && window.customElements) { 76 | customElements.define('sans-h1', SansH1); 77 | } 78 | 79 | export default SansH1; 80 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Header.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/HeaderComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * HeaderComponent.js 3 | * A reusable sans-header web component that can be used in any HTML context 4 | */ 5 | 6 | class SansHeader extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.header = this.shadowRoot.querySelector('header'); 19 | this.header.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('header-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-header', SansHeader); 74 | } 75 | 76 | export default SansHeader; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Img.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/ImgComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ImgComponent.js 3 | * A reusable sans-img web component that can be used in any HTML context 4 | */ 5 | 6 | class SansImg extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'src', 'alt', 'width', 'height', 'loading', 'decoding', 'role', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.img = this.shadowRoot.querySelector('img'); 19 | this.img.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('img-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const src = this.getAttribute('src') || ''; 42 | const alt = this.getAttribute('alt') || ''; 43 | const width = this.getAttribute('width') || ''; 44 | const height = this.getAttribute('height') || ''; 45 | const loading = this.getAttribute('loading') || ''; 46 | const decoding = this.getAttribute('decoding') || ''; 47 | const role = this.getAttribute('role') || ''; 48 | const ariaLabel = this.getAttribute('aria-label') || ''; 49 | const dataTestid = this.getAttribute('data-testid') || ''; 50 | 51 | this.shadowRoot.innerHTML = ` 52 | 62 | 76 | `; 77 | } 78 | } 79 | 80 | // Register the custom element if we're in a browser environment 81 | if (typeof window !== 'undefined' && window.customElements) { 82 | customElements.define('sans-img', SansImg); 83 | } 84 | 85 | export default SansImg; 86 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/LabelComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LabelComponent.js 3 | * A reusable sans-label web component that can be used in any HTML context 4 | */ 5 | 6 | class SansLabel extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'for', 'form', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.label = this.shadowRoot.querySelector('label'); 19 | 20 | // Add event listeners 21 | this.label.addEventListener('click', (e) => { 22 | // Dispatch a custom event 23 | this.dispatchEvent(new CustomEvent('label-click', { 24 | bubbles: true, 25 | composed: true, 26 | detail: { source: this } 27 | })); 28 | }); 29 | } 30 | 31 | attributeChangedCallback(name, oldValue, newValue) { 32 | if (oldValue === newValue) return; 33 | 34 | if (this.shadowRoot) { 35 | this.render(); 36 | } 37 | } 38 | 39 | render() { 40 | const id = this.getAttribute('id') || ''; 41 | const className = this.getAttribute('class') || ''; 42 | const style = this.getAttribute('style') || ''; 43 | const forAttr = this.getAttribute('for') || ''; 44 | const form = this.getAttribute('form') || ''; 45 | const dataTestid = this.getAttribute('data-testid') || ''; 46 | 47 | this.shadowRoot.innerHTML = ` 48 | 61 | 71 | `; 72 | } 73 | } 74 | 75 | // Register the custom element if we're in a browser environment 76 | if (typeof window !== 'undefined' && window.customElements) { 77 | customElements.define('sans-label', SansLabel); 78 | } 79 | 80 | export default SansLabel; 81 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Main.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/MainComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * MainComponent.js 3 | * A reusable sans-main web component that can be used in any HTML context 4 | */ 5 | 6 | class SansMain extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.main = this.shadowRoot.querySelector('main'); 19 | this.main.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('main-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-main', SansMain); 74 | } 75 | 76 | export default SansMain; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Nav.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/NavComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NavComponent.js 3 | * A reusable sans-nav web component that can be used in any HTML context 4 | */ 5 | 6 | class SansNav extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.nav = this.shadowRoot.querySelector('nav'); 19 | this.nav.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('nav-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 | 67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-nav', SansNav); 74 | } 75 | 76 | export default SansNav; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/OptionComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OptionComponent.js 3 | * A reusable sans-option web component that can be used within sans-select 4 | */ 5 | 6 | class SansOption extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'value', 'disabled', 'selected', 'label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (oldValue === newValue) return; 22 | 23 | if (this.shadowRoot) { 24 | this.render(); 25 | } 26 | } 27 | 28 | render() { 29 | const id = this.getAttribute('id') || ''; 30 | const className = this.getAttribute('class') || ''; 31 | const style = this.getAttribute('style') || ''; 32 | const value = this.getAttribute('value') || ''; 33 | const disabled = this.hasAttribute('disabled') ? 'disabled' : ''; 34 | const selected = this.hasAttribute('selected') ? 'selected' : ''; 35 | const label = this.getAttribute('label') || ''; 36 | const dataTestid = this.getAttribute('data-testid') || ''; 37 | 38 | this.shadowRoot.innerHTML = ` 39 | 44 | 56 | `; 57 | } 58 | } 59 | 60 | // Register the custom element if we're in a browser environment 61 | if (typeof window !== 'undefined' && window.customElements) { 62 | customElements.define('sans-option', SansOption); 63 | } 64 | 65 | export default SansOption; 66 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/P.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |

25 | 26 |

27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/PComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PComponent.js 3 | * A reusable sans-p web component that can be used in any HTML context 4 | */ 5 | 6 | class SansP extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.p = this.shadowRoot.querySelector('p'); 19 | this.p.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('p-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 57 |

66 | 67 |

68 | `; 69 | } 70 | } 71 | 72 | // Register the custom element if we're in a browser environment 73 | if (typeof window !== 'undefined' && window.customElements) { 74 | customElements.define('sans-p', SansP); 75 | } 76 | 77 | export default SansP; 78 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/Section.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/SectionComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SectionComponent.js 3 | * A reusable sans-section web component that can be used in any HTML context 4 | */ 5 | 6 | class SansSection extends HTMLElement { 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | } 11 | 12 | static get observedAttributes() { 13 | return ['id', 'class', 'style', 'role', 'tabindex', 'aria-label', 'data-testid']; 14 | } 15 | 16 | connectedCallback() { 17 | this.render(); 18 | this.section = this.shadowRoot.querySelector('section'); 19 | this.section.addEventListener('click', (e) => { 20 | // Dispatch a custom event 21 | this.dispatchEvent(new CustomEvent('section-click', { 22 | bubbles: true, 23 | composed: true, 24 | detail: { source: this } 25 | })); 26 | }); 27 | } 28 | 29 | attributeChangedCallback(name, oldValue, newValue) { 30 | if (oldValue === newValue) return; 31 | 32 | if (this.shadowRoot) { 33 | this.render(); 34 | } 35 | } 36 | 37 | render() { 38 | const id = this.getAttribute('id') || ''; 39 | const className = this.getAttribute('class') || ''; 40 | const style = this.getAttribute('style') || ''; 41 | const role = this.getAttribute('role') || ''; 42 | const tabindex = this.getAttribute('tabindex') || ''; 43 | const ariaLabel = this.getAttribute('aria-label') || ''; 44 | const dataTestid = this.getAttribute('data-testid') || ''; 45 | 46 | this.shadowRoot.innerHTML = ` 47 | 56 |
65 | 66 |
67 | `; 68 | } 69 | } 70 | 71 | // Register the custom element if we're in a browser environment 72 | if (typeof window !== 'undefined' && window.customElements) { 73 | customElements.define('sans-section', SansSection); 74 | } 75 | 76 | export default SansSection; 77 | -------------------------------------------------------------------------------- /packages/ui/src/components/html5/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI - HTML5 Components 3 | * Exports all HTML5 components for easy importing 4 | */ 5 | 6 | import { registerComponent } from '../../lib/components/ComponentFactory.js'; 7 | import ButtonComponent from './ButtonComponent.js'; 8 | 9 | // Svelte Components 10 | export { default as Button } from './Button.svelte'; 11 | // Add other Svelte components as they are implemented 12 | // export { default as P } from './P.svelte'; 13 | // export { default as H1 } from './H1.svelte'; 14 | // export { default as A } from './A.svelte'; 15 | // export { default as Img } from './Img.svelte'; 16 | // export { default as Section } from './Section.svelte'; 17 | // export { default as Article } from './Article.svelte'; 18 | // export { default as Header } from './Header.svelte'; 19 | // export { default as Footer } from './Footer.svelte'; 20 | // export { default as Nav } from './Nav.svelte'; 21 | // export { default as Aside } from './Aside.svelte'; 22 | // export { default as Main } from './Main.svelte'; 23 | 24 | // Web Components 25 | export { default as ButtonComponent } from './ButtonComponent.js'; 26 | // Add other Web Components as they are implemented 27 | // export { default as PComponent } from './PComponent.js'; 28 | // export { default as H1Component } from './H1Component.js'; 29 | // export { default as AComponent } from './AComponent.js'; 30 | // export { default as ImgComponent } from './ImgComponent.js'; 31 | // export { default as SectionComponent } from './SectionComponent.js'; 32 | // export { default as ArticleComponent } from './ArticleComponent.js'; 33 | // export { default as HeaderComponent } from './HeaderComponent.js'; 34 | // export { default as FooterComponent } from './FooterComponent.js'; 35 | // export { default as NavComponent } from './NavComponent.js'; 36 | // export { default as AsideComponent } from './AsideComponent.js'; 37 | // export { default as MainComponent } from './MainComponent.js'; 38 | 39 | // Form-related Components 40 | // export { default as FormComponent } from './FormComponent.js'; 41 | // export { default as LabelComponent } from './LabelComponent.js'; 42 | // export { default as InputComponent } from './InputComponent.js'; 43 | // export { default as SelectComponent } from './SelectComponent.js'; 44 | // export { default as OptionComponent } from './OptionComponent.js'; 45 | // export { default as TextareaComponent } from './TextareaComponent.js'; 46 | 47 | // Component mapping for registration 48 | const COMPONENTS = [ 49 | { name: 'sans-button', component: ButtonComponent }, 50 | // Add other components as they are implemented 51 | ]; 52 | 53 | /** 54 | * Register all Web Components if in browser environment 55 | */ 56 | export function registerAllComponents() { 57 | if (typeof window !== 'undefined' && window.customElements) { 58 | // Register each component 59 | COMPONENTS.forEach(({ name, component }) => { 60 | registerComponent(name, component); 61 | }); 62 | } 63 | } 64 | 65 | // Auto-register components if in a browser environment 66 | if (typeof window !== 'undefined') { 67 | registerAllComponents(); 68 | } 69 | -------------------------------------------------------------------------------- /packages/ui/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI - Main entry point 3 | * A cross-platform UI library with Svelte and Web Components for native UI rendering 4 | */ 5 | 6 | // Export all HTML5 components 7 | export * from './components/html5/index.js'; 8 | 9 | // Export platform detection utilities 10 | export { 11 | PLATFORM, 12 | OS, 13 | detectPlatform, 14 | detectOS, 15 | isDesktop, 16 | isMobile, 17 | isWeb, 18 | getPlatformInfo 19 | } from './lib/platform/PlatformDetector.js'; 20 | 21 | // Export component factory functions 22 | export { 23 | createButton, 24 | createLabel, 25 | createTextInput, 26 | createImage, 27 | createList, 28 | createContainer, 29 | registerComponent, 30 | createSvelteAction, 31 | getNativeUI, 32 | getPlatformInfo as getComponentPlatformInfo 33 | } from './lib/components/ComponentFactory.js'; 34 | 35 | // Export native UI adapter 36 | export { createNativeUI, NativeUIAdapter } from './lib/native/NativeUIAdapter.js'; 37 | 38 | // Export version information 39 | export const VERSION = '1.0.0'; 40 | 41 | /** 42 | * Initialize the Sans UI library 43 | * @param {Object} options - Initialization options 44 | */ 45 | export function initialize(options = {}) { 46 | // Register all Web Components 47 | if (typeof window !== 'undefined' && window.customElements) { 48 | // Import and register all components 49 | import('./components/html5/index.js').then(module => { 50 | if (typeof module.registerAllComponents === 'function') { 51 | module.registerAllComponents(); 52 | } 53 | }).catch(err => { 54 | console.error('Error registering Sans UI components:', err); 55 | }); 56 | } 57 | 58 | // Return the initialized state 59 | return { 60 | version: VERSION, 61 | initialized: true, 62 | options 63 | }; 64 | } 65 | 66 | // Auto-initialize if in a browser environment 67 | if (typeof window !== 'undefined') { 68 | initialize(); 69 | } 70 | 71 | export default { 72 | VERSION, 73 | initialize 74 | }; 75 | -------------------------------------------------------------------------------- /packages/ui/src/lib/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sans UI Component Abstraction Layer 3 | * 4 | * This module exports the component abstraction layer for Sans UI components, 5 | * making it easy to create and manage components across different platforms. 6 | */ 7 | 8 | import ComponentAdapter from './ComponentAdapter'; 9 | import ComponentFactory from './ComponentFactory'; 10 | 11 | // Export the component abstraction layer 12 | export { 13 | ComponentAdapter, 14 | ComponentFactory 15 | }; 16 | 17 | // Default export for convenience 18 | export default ComponentFactory; 19 | -------------------------------------------------------------------------------- /packages/ui/src/lib/webview/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for Qt WebEngine and other platform-specific objects 3 | */ 4 | 5 | // Qt WebEngine types 6 | interface QWebChannelTransport { 7 | send: (message: any) => void; 8 | onmessage: (callback: (event: MessageEvent) => void) => void; 9 | } 10 | 11 | interface QtObject { 12 | webChannelTransport: QWebChannelTransport; 13 | } 14 | 15 | interface QWebChannelObject { 16 | [key: string]: any; 17 | } 18 | 19 | declare class QWebChannel { 20 | constructor(transport: QWebChannelTransport, callback: (channel: { objects: QWebChannelObject }) => void); 21 | } 22 | 23 | // Extend Window interface to include platform-specific objects 24 | declare interface Window { 25 | // Qt WebEngine specific 26 | qt?: QtObject; 27 | QWebChannel?: typeof QWebChannel; 28 | bridge?: any; 29 | bridgeReady?: (bridge: any) => void; 30 | 31 | // iOS WKWebView specific 32 | webkit?: { 33 | messageHandlers: { 34 | [key: string]: { 35 | postMessage: (message: any) => void; 36 | }; 37 | }; 38 | }; 39 | receiveNativeMessage?: (messageJson: string) => void; 40 | 41 | // Microsoft WebView2 specific 42 | chrome?: any; 43 | MSStream?: any; 44 | } 45 | 46 | // Declare the WebViewJS module 47 | declare module '@webviewjs/webview' { 48 | export default class WebViewJS { 49 | constructor(url: string, options?: any); 50 | element: HTMLElement; 51 | navigate(url: string): void; 52 | reload(): void; 53 | executeJavaScript(code: string): Promise; 54 | destroy(): void; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/ui/static/logos/api/favicon.sans-api.svg: -------------------------------------------------------------------------------- 1 | API{{ -------------------------------------------------------------------------------- /packages/ui/static/logos/api/favicon.sans-api.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/api/logo.sans-api.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/api/logo.sans-api.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/db/favicon.db.svg: -------------------------------------------------------------------------------- 1 | DB; -------------------------------------------------------------------------------- /packages/ui/static/logos/db/favicon.db.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/db/logo.sans-db.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/ui/favicon.sans-ui.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/static/logos/ui/favicon.sans-ui.white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "declarationDir": "./dist", 8 | "emitDeclarationOnly": true, 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "types": ["svelte", "node"], 18 | "outDir": "./dist" 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["node_modules", "dist"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/ui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | resolve: { 8 | alias: { 9 | '@': path.resolve(__dirname, './src'), 10 | }, 11 | }, 12 | optimizeDeps: { 13 | exclude: [ 14 | '@nodegui/nodegui', 15 | '@nativescript/core', 16 | ], 17 | }, 18 | build: { 19 | rollupOptions: { 20 | external: [ 21 | '@nodegui/nodegui', 22 | '@nativescript/core', 23 | ], 24 | }, 25 | }, 26 | server: { 27 | fs: { 28 | strict: false, 29 | }, 30 | }, 31 | define: { 32 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 33 | 'global': 'window', 34 | }, 35 | }); -------------------------------------------------------------------------------- /packages/ui/vite.docs.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | import path from 'path'; 4 | 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | root: 'examples', 8 | base: './', 9 | resolve: { 10 | alias: { 11 | '@': path.resolve(__dirname, './src'), 12 | }, 13 | }, 14 | optimizeDeps: { 15 | exclude: [ 16 | '@nodegui/nodegui', 17 | '@nativescript/core', 18 | ], 19 | }, 20 | build: { 21 | outDir: '../dist/docs', 22 | emptyOutDir: true, 23 | rollupOptions: { 24 | external: [ 25 | '@nodegui/nodegui', 26 | '@nativescript/core', 27 | ], 28 | input: { 29 | main: path.resolve(__dirname, 'examples/index.html'), 30 | button: path.resolve(__dirname, 'examples/button/button.html'), 31 | buttonSvelte: path.resolve(__dirname, 'examples/button/Button.svelte'), 32 | div: path.resolve(__dirname, 'examples/div/div.html'), 33 | form: path.resolve(__dirname, 'examples/form-example/index.html'), 34 | camera: path.resolve(__dirname, 'examples/camera-subscribe-example/index.html'), 35 | desktop: path.resolve(__dirname, 'examples/desktop-example/index.html'), 36 | mobile: path.resolve(__dirname, 'examples/mobile-example/index.html'), 37 | uiElements: path.resolve(__dirname, 'examples/ui-elements/index.html'), 38 | }, 39 | }, 40 | }, 41 | server: { 42 | fs: { 43 | strict: false, 44 | }, 45 | open: '/index.html', 46 | }, 47 | define: { 48 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'), 49 | 'global': 'window', 50 | }, 51 | }); --------------------------------------------------------------------------------