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>The following code demonstrates how to integrate the ODS Selector Widget into a React application with TypeScript.
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;Key Points:
defineCustomElements(window): Registers the custom web component with the browser. This must be called once before rendering any<ods-selector-widget>elements.projectattribute: Sets the project name for the widget.service-urlattribute: Defines the API endpoint. Use${project}as a template variable that gets replaced with the project name.
To enable TypeScript support for the custom element, add this to your type declarations file:
// Add to your declarations file (e.g., custom-elements.d.ts)
declare module 'react' {
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;
}- An npmjs.com account with access to the
@opendevstackscope. - You must be logged in to the npm registry:
npm login
The dist/ and loader/ directories (declared in the files field of package.json) are generated by the build step and must be up to date before publishing.
npm run buildUpdate the version field in package.json following Semantic Versioning, then record the change in CHANGELOG.md.
# patch: bug fixes → 0.2.0 → 0.2.1
npm version patch
# minor: new backwards-compatible features → 0.2.0 → 0.3.0
npm version minor
# major: breaking changes → 0.2.0 → 1.0.0
npm version majornpm version automatically updates package.json and creates a git tag.
Make sure all tests pass before publishing.
npm testThe package is scoped to @opendevstack, so you need to explicitly opt in to publishing it as a public package:
npm publish --access publicOnly the contents of the files array in package.json are included in the tarball:
| Directory | Contents |
|---|---|
dist/ |
Compiled component bundles, type declarations, and collection manifest |
loader/ |
Custom Elements loader for lazy-loading in Angular/React apps |
You can preview the exact file list without publishing by running:
npm pack --dry-run- 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.