A flexible and customizable web component for displaying platform navigation and resource links. Built with Stencil, this component provides a clean interface for organizing and accessing multiple platform resources in a project-based context.
- 🎨 Fully Customizable: Extensive CSS custom properties for theming
- 🔌 Framework Agnostic: Works both with Angular and React
- 🔄 Dynamic Content: Fetches platform data from a service URL
- 🦴 Loading States: Built-in skeleton loader and error handling
- 🔗 Platform Selector: Dedicated platform icons with enable/disable states
- 📑 Sectioned Layout: Organize links into logical sections with tooltips
- 🔀 Multiple Link Types: Support for platform, project (internal), and external links with appropriate icons
npm install @opendevstack/ods-selector-widgetFor Angular with NgModule (traditional setup):
In your app.module.ts:
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
import { AppComponent } from './app.component';
defineCustomElements(window);
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }For Angular with Standalone Components:
In your main.ts:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
defineCustomElements(window);
bootstrapApplication(AppComponent, {
providers: []
});In your app.component.ts:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// Component logic
}Note: If you encounter the error
✘ [ERROR] No loader is configured for ".map" files, you need to update yourangular.jsonconfiguration.
Add the following to your architect.build.options in angular.json:
{
"architect": {
"build": {
"options": {
"customWebpackConfig": {
"loader": {
".map": "empty"
}
}
}
}
}
}<ods-selector-widget
project="My Project"
service-url="https://api.example.com/platforms/{{project}}">
</ods-selector-widget>import React from 'react';
import { defineCustomElements } from '@opendevstack/ods-selector-widget/loader';
// Call this once in your app initialization
defineCustomElements(window);
function App() {
return (
<div>
<ods-selector-widget
project="My Project"
service-url="https://api.example.com/platforms/${project}"
/>
</div>
);
}
export default App;For better TypeScript support in React:
// Add to your declarations file (e.g., custom-elements.d.ts)
declare namespace JSX {
interface IntrinsicElements {
'ods-selector-widget': {
project?: string;
'service-url'?: string;
};
}
}Note: If you encounter the error
No loader is configured for ".map" filesin a React + Vite project, update yourvite.config.jsorvite.config.tsto exclude.mapfiles from dependency optimization.
Add the following to your Vite config:
// vite.config.js or vite.config.ts
export default {
optimizeDeps: {
exclude: ['*.map']
}
}| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
project |
project |
string |
undefined |
The project name displayed in the widget header |
serviceUrl |
service-url |
string |
undefined |
The service URL to fetch platform data. Use ${project} as a placeholder that will be replaced with the project name |
<ods-selector-widget
project="PROJECT1"
service-url="https://api.myapp.com/platforms/${project}">
</ods-selector-widget>The component expects the service URL to return JSON data in the following format:
{
"sections": [
{
"section": "Select your platform",
"tooltip": "",
"links": [
{
"label": "Platform One",
"url": "https://platform1.com",
"abbreviation": "P1",
"type": "platform"
},
{
"label": "Platform Two",
"url": "https://platform2.com",
"abbreviation": "P2",
"type": "platform",
"disabled": true
},
{
"label": "Platform Three",
"url": "https://platform3.com",
"abbreviation": "P3",
"type": "platform",
"disabled": true
}
]
},
{
"section": "Documentation",
"tooltip": "Access project documentation and guides",
"links": [
{
"label": "API Reference",
"url": "https://docs.example.com/api",
"tooltip": "Complete API documentation",
"type": "external"
},
{
"label": "Getting Started",
"url": "https://docs.example.com/getting-started",
"tooltip": "Quick start guide",
"type": "project"
}
]
},
{
"section": "Tools",
"tooltip": "Development and monitoring tools",
"links": [
{
"label": "Dashboard",
"url": "https://dashboard.example.com",
"tooltip": "Project dashboard",
"type": "external"
}
]
}
]
}- sections (
array): Array of content sections to display. Each section can contain either platform links or regular resource links.section(string): Section titletooltip(string, optional): Tooltip text for the section headerlinks(array): Array of links in this sectionlabel(string): Link display texturl(string): Link destination URLtype(string): Link type -"platform","project", or"external""platform": Renders as platform selector icons (when all links in a section are platform type)"project": Renders as list item with internal link arrow icon"external": Renders as list item with external link squared arrow icon
tooltip(string, optional): Tooltip text for the link (shown on hover for non-platform links)abbreviation(string, optional): Short text shown in platform icon (required fortype: "platform")disabled(boolean, optional): Iftrue, the platform link is disabled and shown as non-clickable (only applicable fortype: "platform")
The component automatically adapts its rendering based on the link types within each section:
-
Platform Section: If all links in a section have
type: "platform", they are rendered as clickable platform icons in a horizontal layout. This is typically used for the main platform selector section. -
Link List Section: If a section contains any non-platform links (
type: "project"ortype: "external"), all links in that section are rendered as a vertical list with appropriate arrow icons split in two columns.
Example:
{
"sections": [
{
"section": "Platforms",
"links": [
{ "label": "Platform 1", "url": "...", "abbreviation": "P1", "type": "platform" },
{ "label": "Platform 2", "url": "...", "abbreviation": "P2", "type": "platform" }
]
}
]
}The example above renders as platform icons
{
"sections": [
{
"section": "Resources",
"links": [
{ "label": "Documentation", "url": "...", "type": "external" },
{ "label": "Dashboard", "url": "...", "type": "project" }
]
}
]
}The example above renders as a list with arrow/squared arrow icons.
The component uses CSS custom properties (CSS variables) for easy theming. You can override these in your stylesheet:
ods-selector-widget {
/* Font family */
--ods-selector-widget-font-family: 'Inter', sans-serif;
/* Primary colors */
--ods-selector-widget-color-primary: #08312A;
--ods-selector-widget-color-white: #FFF;
--ods-selector-widget-color-border: #5C5B59;
--ods-selector-widget-color-shadow: rgba(0, 0, 0, 0.05);
--ods-selector-widget-color-shadow-hover: rgba(0, 0, 0, 0.25);
/* Header colors */
--ods-selector-widget-header-bg: #CCFAE5;
--ods-selector-widget-header-text: #08312A;
/* Platform icon colors */
--ods-selector-widget-platform-icon-bg: #08312A;
--ods-selector-widget-platform-icon-text: white;
--ods-selector-widget-first-platform-bg: #008E4C;
--ods-selector-widget-first-platform-text: #ffffff;
--ods-selector-widget-second-platform-bg: #5E71FF;
--ods-selector-widget-second-platform-text: #ffffff;
--ods-selector-widget-third-platform-bg: #00C86B;
--ods-selector-widget-third-platform-text: #ffffff;
/* Disabled state colors */
--ods-selector-widget-disabled-bg: #E5E3DE;
--ods-selector-widget-disabled-text: #898885;
/* Section colors */
--ods-selector-widget-section-text: #08312A;
--ods-selector-widget-divider-color: #D3D3D3;
/* Link colors */
--ods-selector-widget-link-text: #08312A;
--ods-selector-widget-link-hover: #6B8375;
/* Error section colors */
--ods-selector-widget-error-text: #051D19;
--ods-selector-widget-error-button-border: #08312A;
--ods-selector-widget-error-button-text: #08312A;
--ods-selector-widget-error-button-hover-text: #6B8375;
--ods-selector-widget-error-button-hover-border: #6B8375;
/* Tooltip colors */
--ods-selector-widget-tooltip-bg: #051D19;
--ods-selector-widget-tooltip-text: #fff;
/* Skeleton loader colors */
--ods-selector-widget-skeleton-bg: #F6F5F3;
--ods-selector-widget-skeleton-shimmer: #e4e3e2;
}ods-selector-widget {
--ods-selector-widget-font-family: "YourCustomFont", sans-serif;
--ods-selector-widget-color-primary: #2563eb;
--ods-selector-widget-header-bg: #dbeafe;
--ods-selector-widget-header-text: #1e40af;
--ods-selector-widget-first-platform-bg: #10b981;
--ods-selector-widget-second-platform-bg: #8b5cf6;
--ods-selector-widget-third-platform-bg: #f59e0b;
}- Node.js (v14 or higher)
- npm (v6 or higher)
# Clone the repository
git clone https://github.com/opendevstack/ods-selector-widget.git
cd ods-selector-widget
# Install dependencies
npm install# Start development server with live reload
npm start
# Build for production
npm run build
# Run tests
npm test
# Run tests in watch mode
npm run test.watch
# Generate a new component
npm run generateods-selector-widget/
├── src/
│ ├── components/
│ │ └── ods-selector-widget/
│ │ ├── ods-selector-widget.tsx # Component logic
│ │ ├── ods-selector-widget.css # Component styles
│ │ ├── ods-selector-widget.spec.ts # Unit tests
│ │ └── ods-selector-widget.e2e.ts # E2E tests
│ ├── index.html # Demo page
│ └── index.ts # Entry point
├── dist/ # Build output
├── www/ # Dev server output
└── stencil.config.ts # Stencil configuration
The component includes both unit and end-to-end tests:
# Run all tests
npm test
# Run only unit tests
npm run test -- --spec
# Run only e2e tests
npm run test -- --e2e
# Generate coverage report
npm test -- --coverageThis component works in all modern browsers that support:
- Custom Elements V1
- Shadow DOM
- ES Modules
For older browsers, you may need to include polyfills.
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write clear, descriptive commit messages
- Add tests for new features
- Update documentation as needed
- Follow the existing code style
- Ensure all tests pass before submitting PR
This project is licensed under the Apache License - see the LICENSE file for details.
If you encounter any issues or have questions:
- Open an issue on GitHub Issues
- Check the Stencil documentation
Built with Stencil - A toolchain for building reusable, scalable Design Systems.