diff --git a/packages/storybook/package.json b/packages/storybook/package.json
index 0f36fce..183b59a 100644
--- a/packages/storybook/package.json
+++ b/packages/storybook/package.json
@@ -49,9 +49,7 @@
"devDependencies": {
"@phase2/outline-adopted-stylesheets-controller": "^1.0.4",
"@phase2/outline-config": "^0.0.14",
- "@phase2/outline-core": "^0.2.7",
"@phase2/outline-core-alert": "^0.0.4",
- "@phase2/outline-core-link": "^0.0.16",
"@phase2/outline-docs": "^0.0.21",
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
diff --git a/packages/storybook/src/components/shared/outline-link/README.md b/packages/storybook/src/components/shared/outline-example/README.md
similarity index 100%
rename from packages/storybook/src/components/shared/outline-link/README.md
rename to packages/storybook/src/components/shared/outline-example/README.md
diff --git a/packages/storybook/src/components/shared/outline-example/docs/outline-example.mdx b/packages/storybook/src/components/shared/outline-example/docs/outline-example.mdx
new file mode 100644
index 0000000..4d3eef6
--- /dev/null
+++ b/packages/storybook/src/components/shared/outline-example/docs/outline-example.mdx
@@ -0,0 +1,28 @@
+import { Meta } from '@storybook/addon-docs';
+import { OutlineExample } from '../outline-example';
+
+
+
+# `OutlineExample`: `outline-example`
+
+> This is an `outline-example` component.
+> This component is a wrapper around an anchor tag that provides **encapsulated** and **global** styles for links.
+> This component extends `LitElement` and adds custom styling.
+
+Outline
+
+```html
+
+ Outline
+
+```
diff --git a/packages/storybook/src/components/shared/outline-example/outline-example.ts b/packages/storybook/src/components/shared/outline-example/outline-example.ts
new file mode 100644
index 0000000..6a4ba0e
--- /dev/null
+++ b/packages/storybook/src/components/shared/outline-example/outline-example.ts
@@ -0,0 +1,49 @@
+import { customElement } from 'lit/decorators.js';
+import { LitElement, html } from 'lit';
+
+import globalStyles from './styles/outline-example.global.css?inline';
+import encapsulatedStyles from './styles/outline-example.encapsulated.css?inline';
+import { AdoptedStyleSheets } from '../../../controllers/adopted-stylesheets';
+
+/**
+ * Example component.
+ *
+ * @see {@link https://lit.dev/docs/components/defining/ | Defining components}
+ * @see {@link https://www.npmjs.com/package/@phase2/outline-example-alert | @phase2/outline-example-alert }
+ * @element outline-example
+ * @extends OutlineExample
+ */
+@customElement('outline-example')
+export class OutlineExample extends LitElement {
+ adoptedStyleSheets = new AdoptedStyleSheets(this, {
+ globalCSS: globalStyles,
+ encapsulatedCSS: encapsulatedStyles,
+ });
+
+ /**
+ * Wrapping the rendered content in a div with the class "encapsulated-container".
+ * This demonstrates the difference between encapsulated and light DOM styling.
+ *
+ * @category rendering
+ * @returns {TemplateResult} The template to render.
+ */
+ render() {
+ return html`
+
+
+
+ `;
+ }
+}
+
+/**
+ * TypeScript declaration extends the HTMLElementTagNameMap interface, adding the web component.
+ * This enhances type checking and autocompletion in IDEs.
+ *
+ * @see {@link https://lit.dev/docs/components/defining/#typescript-typings | Providing good TypeScript typings}
+ */
+declare global {
+ interface HTMLElementTagNameMap {
+ 'outline-example': OutlineExample;
+ }
+}
diff --git a/packages/storybook/src/components/shared/outline-link/styles/outline-link.encapsulated.css b/packages/storybook/src/components/shared/outline-example/styles/outline-example.encapsulated.css
similarity index 100%
rename from packages/storybook/src/components/shared/outline-link/styles/outline-link.encapsulated.css
rename to packages/storybook/src/components/shared/outline-example/styles/outline-example.encapsulated.css
diff --git a/packages/storybook/src/components/shared/outline-link/styles/outline-link.global.css b/packages/storybook/src/components/shared/outline-example/styles/outline-example.global.css
similarity index 53%
rename from packages/storybook/src/components/shared/outline-link/styles/outline-link.global.css
rename to packages/storybook/src/components/shared/outline-example/styles/outline-example.global.css
index 5edb5c1..d68b37f 100644
--- a/packages/storybook/src/components/shared/outline-link/styles/outline-link.global.css
+++ b/packages/storybook/src/components/shared/outline-example/styles/outline-example.global.css
@@ -1,4 +1,4 @@
-outline-link {
+outline-example {
/* This is just here to demonstrate using a nested import in a CSS file. */
- @nested-import './outline-link.imported.css';
+ @nested-import './outline-example.imported.css';
}
diff --git a/packages/storybook/src/components/shared/outline-link/styles/outline-link.imported.css b/packages/storybook/src/components/shared/outline-example/styles/outline-example.imported.css
similarity index 100%
rename from packages/storybook/src/components/shared/outline-link/styles/outline-link.imported.css
rename to packages/storybook/src/components/shared/outline-example/styles/outline-example.imported.css
diff --git a/packages/storybook/src/components/shared/outline-link/docs/outline-link.mdx b/packages/storybook/src/components/shared/outline-link/docs/outline-link.mdx
deleted file mode 100644
index d464cd0..0000000
--- a/packages/storybook/src/components/shared/outline-link/docs/outline-link.mdx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Meta } from '@storybook/addon-docs';
-import { OutlineLink } from '../outline-link';
-
-
-
-# `OutlineLink`: `outline-link`
-
-> This is an `outline-link` component.
-> This component is a wrapper around an anchor tag that provides a consistent style for links.
-> This component extends `OutlineCoreLink` and adds custom styling.
-
-Outline
-
-```html
-
- Outline
-
-```
diff --git a/packages/storybook/src/components/shared/outline-link/outline-link.ts b/packages/storybook/src/components/shared/outline-link/outline-link.ts
deleted file mode 100644
index 2dbb9f2..0000000
--- a/packages/storybook/src/components/shared/outline-link/outline-link.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { customElement } from 'lit/decorators.js';
-import { html } from 'lit';
-
-import { OutlineCoreLink } from '@phase2/outline-core-link';
-import globalStyles from './styles/outline-link.global.css?inline';
-import encapsulatedStyles from './styles/outline-link.encapsulated.css?inline';
-import { AdoptedStylesheets } from '@phase2/outline-adopted-stylesheets-controller';
-
-/**
- * Link component.
- *
- * @see {@link https://lit.dev/docs/components/defining/ | Defining components}
- * @see {@link https://www.npmjs.com/package/@phase2/outline-core-alert | @phase2/outline-core-alert }
- * @element outline-link
- * @extends OutlineCoreLink
- */
-@customElement('outline-link')
-export class OutlineLink extends OutlineCoreLink {
- GlobalStylesheets: AdoptedStylesheets | undefined = new AdoptedStylesheets(
- this,
- globalStyles,
- document,
- );
-
- EncapsulatedStylesheets: AdoptedStylesheets | undefined;
-
- createRenderRoot() {
- const root = super.createRenderRoot();
- this.EncapsulatedStylesheets = this.shadowRoot
- ? new AdoptedStylesheets(this, encapsulatedStyles, this.shadowRoot)
- : undefined;
- return root;
- }
-
- /**
- * Overrides the render method from the parent class to wrap the rendered content
- * in a div with the class "encapsulated-container". This demonstrates the difference
- * between encapsulated and light DOM styling.
- *
- * @category rendering
- * @returns {TemplateResult} The template to render.
- */
- render() {
- return html` ${super.render()}
`;
- }
-}
-
-/**
- * TypeScript declaration extends the HTMLElementTagNameMap interface, adding the web component.
- * This enhances type checking and autocompletion in IDEs.
- *
- * @see {@link https://lit.dev/docs/components/defining/#typescript-typings | Providing good TypeScript typings}
- */
-declare global {
- interface HTMLElementTagNameMap {
- 'outline-link': OutlineLink;
- }
-}
diff --git a/packages/storybook/src/controllers/adopted-stylesheets.ts b/packages/storybook/src/controllers/adopted-stylesheets.ts
new file mode 100644
index 0000000..c87dbb6
--- /dev/null
+++ b/packages/storybook/src/controllers/adopted-stylesheets.ts
@@ -0,0 +1,82 @@
+import { ReactiveController, ReactiveControllerHost } from 'lit';
+
+/**
+ * A controller for managing adopted stylesheets in a Lit element.
+ * This allows for styles to be dynamically applied to the component's
+ * light DOM or shadow DOM.
+ */
+export class AdoptedStyleSheets implements ReactiveController {
+ // The host element that the controller is associated with.
+ private host: ReactiveControllerHost & HTMLElement;
+ // An object containing the CSS to be applied globally or encapsulated within the shadow DOM.
+ private css: { globalCSS?: string; encapsulatedCSS?: string };
+
+ // A set to track the stylesheets applied to the light DOM.
+ private static appliedLightDomStylesheets: Set = new Set();
+
+ /**
+ * Constructs an instance of the AdoptedStyleSheets controller.
+ * @param host The host element that the controller will be associated with.
+ * @param css An object containing optional global and encapsulated CSS strings.
+ */
+ constructor(
+ host: ReactiveControllerHost & HTMLElement,
+ css: {
+ globalCSS?: string;
+ encapsulatedCSS?: string;
+ } = {}
+ ) {
+ this.host = host;
+ this.css = css;
+ this.host.addController(this); // Register this instance as a controller for the host element.
+ }
+
+ /**
+ * Applies the given CSS text to the specified target (Document or ShadowRoot).
+ * @param cssText The CSS text to apply.
+ * @param target The target where the CSS should be applied.
+ */
+ private applyCssToDom(cssText: string, target: Document | ShadowRoot) {
+ if (target instanceof Document) {
+ const store = AdoptedStyleSheets.appliedLightDomStylesheets;
+
+ if (store.has(cssText)) {
+ // If the stylesheet has already been applied, no further action is required.
+ return;
+ }
+ store.add(cssText); // Store the stylesheet with the provided key.
+ }
+
+ // Create a new stylesheet and replace its contents with the provided CSS text.
+ const sheet = new CSSStyleSheet();
+ sheet.replaceSync(cssText);
+
+ // Apply the stylesheet to the target's adoptedStyleSheets.
+ target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet];
+ }
+
+ /**
+ * Lifecycle callback called when the host element is connected to the document's DOM.
+ * Applies global and encapsulated CSS to the respective DOM targets.
+ */
+ hostConnected() {
+ if (this.css.globalCSS) {
+ this.applyCssToDom(this.css.globalCSS, document); // Apply global CSS to the document if it exists.
+ }
+
+ // Apply encapsulated CSS to the host's shadow root if it exists.
+ if (this.css.encapsulatedCSS && this.host.shadowRoot) {
+ this.applyCssToDom(this.css.encapsulatedCSS, this.host.shadowRoot); // Apply encapsulated CSS to the host's shadow root if it exists.
+ }
+ }
+
+ /**
+ * Lifecycle callback called when the host element is disconnected from the document's DOM.
+ * Note: When a component with a Shadow DOM is disconnected from the document's DOM, the Shadow DOM is also removed along with the component.
+ * However, for Light DOM styles, they are not removed here because other instances of the component
+ * might still be present on the page and require these styles.
+ */
+ hostDisconnected() {
+ // No action is taken when the host is disconnected.
+ }
+}
diff --git a/packages/storybook/src/controllers/resize-controller.ts b/packages/storybook/src/controllers/resize-controller.ts
new file mode 100644
index 0000000..0dee9bd
--- /dev/null
+++ b/packages/storybook/src/controllers/resize-controller.ts
@@ -0,0 +1,167 @@
+import { ReactiveControllerHost, ReactiveController } from 'lit';
+import { debounce } from '../utilities/debounce';
+
+export type breakpointsRangeType = {
+ min: number;
+ max: number;
+};
+
+interface HostWithResizeController extends ReactiveControllerHost, HTMLElement {
+ resizeController?: ResizeController;
+}
+
+/**
+ * ResizeController class
+ * @implements {ReactiveController}
+ */
+export class ResizeController implements ReactiveController {
+ host: HostWithResizeController;
+ resizeObserver: ResizeObserver;
+ elementToObserve: Element;
+ options: {
+ debounce: number;
+ breakpoints: number[];
+ triggerElement: ReactiveControllerHost & HTMLElement;
+ };
+ currentComponentWidth: number;
+ currentBreakpointRange: number;
+ breakpointsRangeArray: breakpointsRangeType[] = [];
+
+ /**
+ * Create a constructor that takes a host and options
+ * @param {ReactiveControllerHost & Element} host - The host element
+ * @param {{debounce?: number; breakpoints?: number[]}} [options={}] - The options object
+ */
+ constructor(
+ host: HostWithResizeController,
+ options: {
+ debounce?: number;
+ breakpoints?: number[];
+ triggerElement?: ReactiveControllerHost & HTMLElement;
+ } = {}
+ ) {
+ // If component (host) already has resizeController attached - return early
+ // e.g. outline-layout use its ancestor outline-container triggerElement,
+ // but outline-container already added that controller for itself
+ if (host.resizeController) {
+ return;
+ }
+
+ const defaultOptions = {
+ debounce: 0,
+ breakpoints: [768],
+ triggerElement: host,
+ };
+
+ /**
+ * Remove any undefined variables from options object
+ */
+ const filteredOptionsObject = Object.fromEntries(
+ Object.entries(options).filter(([_, value]) => value !== undefined)
+ );
+ this.options = { ...defaultOptions, ...filteredOptionsObject };
+
+ // Store a reference to the host
+ this.host = host;
+ // Register for lifecycle updates
+ host.addController(this);
+
+ this.initializeBreakpointsRangeType();
+ }
+
+ /**
+ * Initialize the breakpoints range array
+ *
+ * The default breakpoints array ([768]) will create this breakpoints range array:
+ * [{min: 0, max: 767}, {min: 768, max: 100000}]
+ *
+ * If custom breakpoints array is provided, (for example [768, 1200, 2000]) this breakpoints range array will be created:
+ * [{min: 0, max: 767}, {min: 768, max: 1199}, {min: 1200, max: 1999}, {min: 2000, max: 100000}]
+ *
+ */
+ initializeBreakpointsRangeType() {
+ // This will allow create an additional breakpoint from the last custom breakpoint to 100000
+ this.options.breakpoints?.push(100000);
+
+ let minBreakpoint = 0;
+ this.options.breakpoints?.forEach(breakpoint => {
+ const newBreakpointRange = {
+ min: minBreakpoint,
+ max: breakpoint - 1,
+ };
+ minBreakpoint = breakpoint;
+ this.breakpointsRangeArray.push(newBreakpointRange);
+ });
+ }
+
+ /**
+ * Called when the host element is connected to the DOM
+ */
+ hostConnected() {
+ if (!this.options.triggerElement.style.display) {
+ // adding `display: block` to :host of component
+ this.options.triggerElement.style.setProperty(
+ 'display',
+ 'var(--style-added-by-resize-controller, block)'
+ );
+ }
+ // Initialize currentBreakpointRange value
+ this.currentComponentWidth = this.options.triggerElement.clientWidth;
+ this.calculateNewBreakpointRange();
+
+ // Create a new ResizeObserver and pass in the function to be called when the element is resized
+ this.resizeObserver = new ResizeObserver(
+ (entries: ResizeObserverEntry[]) => {
+ // Create a debounced version of the onElementResize function
+ debounce(
+ this.onElementResize.bind(this),
+ this.options.debounce
+ )(entries);
+ }
+ );
+
+ // Observe the element for size changes
+ this.resizeObserver.observe(this.options.triggerElement);
+ }
+
+ /**
+ * Called when the host element is disconnected from the DOM
+ */
+ hostDisconnected() {
+ this.resizeObserver.disconnect();
+ }
+
+ /**
+ * Called when the element is resized
+ * @param {ResizeObserverEntry[]} _entries - The ResizeObserverEntry array
+ */
+ onElementResize(_entries: ResizeObserverEntry[]) {
+ this.currentComponentWidth = _entries[0].contentRect.width;
+
+ // skip if width is not yet set
+ if (this.currentComponentWidth) {
+ this.calculateNewBreakpointRange();
+ }
+ }
+
+ /**
+ * Calculate the new breakpoint based on the current width
+ */
+ calculateNewBreakpointRange() {
+ let newBreakpointRange = this.currentBreakpointRange;
+
+ this.breakpointsRangeArray.forEach((breakpoint, index) => {
+ if (
+ this.currentComponentWidth >= breakpoint.min &&
+ this.currentComponentWidth <= breakpoint.max
+ ) {
+ newBreakpointRange = index;
+ }
+ });
+
+ if (newBreakpointRange !== this.currentBreakpointRange) {
+ this.currentBreakpointRange = newBreakpointRange;
+ this.host.requestUpdate();
+ }
+ }
+}
diff --git a/packages/storybook/src/controllers/reveal-trigger.ts b/packages/storybook/src/controllers/reveal-trigger.ts
new file mode 100644
index 0000000..f2303c8
--- /dev/null
+++ b/packages/storybook/src/controllers/reveal-trigger.ts
@@ -0,0 +1,81 @@
+import type { ReactiveController, ReactiveControllerHost } from 'lit';
+
+/**
+ * Options for the RevealTrigger controller.
+ */
+export type Options = {
+ /**
+ * The selector for the elements that trigger the reveal.
+ */
+ triggerSelector: string;
+ /**
+ * The class to add to the elements when they are revealed.
+ */
+ revealClass: string;
+ /**
+ * Whether to retrigger the reveal (by removing the class when the element is no longer in the viewport.)
+ */
+ retrigger?: boolean;
+};
+
+/**
+ * The revealTrigger ReactiveController.
+ *
+ * This controller finds a class on the host element and adds a class when the element is in the viewport.
+ * Very useful for animations - just add the class to the element and the animation will play when the element is in the viewport.
+ *
+ * @param host The host element
+ */
+export class RevealTrigger implements ReactiveController {
+ host: ReactiveControllerHost & HTMLElement;
+ options: Options;
+
+ constructor(host: ReactiveControllerHost & HTMLElement, options: Options) {
+ this.options = options;
+ this.options = {
+ triggerSelector: options.triggerSelector,
+ revealClass: options.revealClass,
+ retrigger: options.retrigger || false,
+ };
+
+ // Store a reference to the host
+ this.host = host;
+ // Register for lifecycle updates
+ host.addController(this);
+ }
+
+ /**
+ * Called when the host element is connected to the DOM.
+ */
+ hostConnected() {
+ this.setRevealClasses(this.host, this.options);
+ }
+
+ /**
+ * Sets the reveal classes on the host element.
+ *
+ * @param host The host element
+ * @param options The options for the controller
+ */
+ setRevealClasses(host: HTMLElement, options: Options) {
+ setTimeout(() => {
+ const elementsToReveal = host.shadowRoot?.querySelectorAll(
+ options.triggerSelector
+ );
+
+ const revealObserver = new IntersectionObserver(entries => {
+ entries.forEach(entry => {
+ if (entry.intersectionRatio > 0) {
+ entry.target.classList.add(options.revealClass);
+ } else if (options.retrigger) {
+ entry.target.classList.remove(options.revealClass);
+ }
+ });
+ });
+
+ elementsToReveal?.forEach(element => {
+ revealObserver.observe(element);
+ });
+ }, 0);
+ }
+}
diff --git a/packages/storybook/src/index.ts b/packages/storybook/src/index.ts
index 45ffa29..6214df1 100644
--- a/packages/storybook/src/index.ts
+++ b/packages/storybook/src/index.ts
@@ -11,7 +11,7 @@
// import { Component1 } from './components/component1';
// import { Component2 } from './components/component2';
// import { OutlineAlert } from './components/shared/outline-alert/outline-alert';
-import { OutlineLink } from './components/shared/outline-link/outline-link';
+import { OutlineExample } from './components/shared/outline-example/outline-example';
// import { OutlineCoreLink } from '@phase2/outline-core-link';
// Add more component imports as needed...
@@ -24,7 +24,7 @@ import { OutlineLink } from './components/shared/outline-link/outline-link';
// Exporting all imported components and controllers for external use
export {
// OutlineAlert,
- OutlineLink,
+ OutlineExample,
// OutlineCoreLink,
// Component1,
// Component2,
diff --git a/packages/storybook/src/utilities/base-path.ts b/packages/storybook/src/utilities/base-path.ts
new file mode 100644
index 0000000..22d747b
--- /dev/null
+++ b/packages/storybook/src/utilities/base-path.ts
@@ -0,0 +1,62 @@
+// This file was copied from: https://github.com/shoelace-style/shoelace/blob/next/src/utilities/base-path.ts
+// Every 'shoelace' occurrence was replaced with 'outline'
+
+let basePath = '';
+
+/** Sets the library's base path to the specified directory. */
+export function setBasePath(path: string) {
+ basePath = path;
+}
+
+/**
+ * Gets the library's base path.
+ *
+ * The base path is used to load assets such as icons and images, so it needs to be set for components to work properly.
+ * By default, this script will look for a script ending in outline.js or outline-eager-loader.js or
+ * outline-lazy-loader.js and set the base path to the directory that contains that file.
+ * To override this behavior, you can add the data-outline attribute to any script on the page
+ * (it probably makes the most sense to attach it to the outline script, but it could also be on a bundle).
+ * The value can be a local folder or it can point to a CORS-enabled endpoint such as a CDN.
+ *
+ *
+ *
+ * Alternatively, you can set the base path manually using the exported setBasePath() function.
+ *
+ * @param subpath - An optional path to append to the base path.
+ */
+export function getBasePath(subpath = '') {
+ if (!basePath) {
+ const scripts = [
+ ...document.getElementsByTagName('script'),
+ ] as HTMLScriptElement[];
+ const configScript = scripts.find(script =>
+ script.hasAttribute('data-outline')
+ );
+
+ if (configScript) {
+ // Use the data-outline attribute
+ setBasePath(configScript.getAttribute('data-outline')!);
+ } else {
+ const fallbackScript = scripts.find(s => {
+ return (
+ /outline(\.min)?\.js($|\?)/.test(s.src) ||
+ /outline-eager-loader(\.min)?\.js($|\?)/.test(s.src) ||
+ /outline-lazy-loader(\.min)?\.js($|\?)/.test(s.src)
+ );
+ });
+ let path = '';
+
+ if (fallbackScript) {
+ path = fallbackScript.getAttribute('src')!;
+ }
+
+ setBasePath(path.split('/').slice(0, -1).join('/'));
+ }
+ }
+
+ // Return the base path without a trailing slash. If one exists, append the subpath separated by a slash.
+ return (
+ basePath.replace(/\/$/, '') +
+ (subpath ? `/${subpath.replace(/^\//, '')}` : ``)
+ );
+}
diff --git a/packages/storybook/src/utilities/debounce.ts b/packages/storybook/src/utilities/debounce.ts
new file mode 100644
index 0000000..96a45ff
--- /dev/null
+++ b/packages/storybook/src/utilities/debounce.ts
@@ -0,0 +1,28 @@
+/**
+ * Debounces a function
+ * @template T
+ * @param {T} func - The function to debounce
+ * @param {number} delay - The delay in milliseconds
+ * @param {boolean} [immediate=false] - Whether to execute the function immediately
+ * @returns {(...args: Parameters) => void} - The debounced function
+ */
+
+export const debounce = ) => void>(
+ func: T,
+ delay: number,
+ immediate = false
+): ((...args: Parameters) => void) => {
+ let timeoutId: ReturnType | undefined = undefined;
+
+ return function debounced(...args: Parameters) {
+ const executeFunc = () => func(...args);
+
+ clearTimeout(timeoutId);
+
+ if (immediate && timeoutId === undefined) {
+ executeFunc();
+ }
+
+ timeoutId = setTimeout(executeFunc, delay);
+ };
+};
diff --git a/packages/storybook/src/utilities/dialog.ts b/packages/storybook/src/utilities/dialog.ts
new file mode 100644
index 0000000..060cb70
--- /dev/null
+++ b/packages/storybook/src/utilities/dialog.ts
@@ -0,0 +1,102 @@
+// code adopted from https://web.dev/building-a-dialog-component/#all-together
+
+// custom events to be added to