arrow-left

Only this pageAll pages
gitbookPowered by GitBook
triangle-exclamation
Couldn't generate the PDF for 239 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

DUKE

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Jira Tickets

DNT-2843 EVCalculator

hashtag
Controls:

Name
Description
Default
Control

inputs*

DNT-2860 Go Green Calculator

chevron-rightNew Featureshashtag

New Simple Calculator Input Warning And Block Explanation**

The need for this ticket derives from a fictional unit of energy duke created called a block which is "100 kWh of 'green' energy". Because a block requires whole-number outputs that necessitates a minimum input value; which arguably needs to be explained to the user, and if the user fails to heed the instructions... have the warning change color to red if they input a lower number.

Udemy

https://www.udemy.com/course/react-and-typescript-build-a-portfolio-project

Site Navigation

Navigate my docs

hashtag
🗺 Site Navigation

hashtag

DNT-2657 Lists must be contained within semantically correct containers

src/components/MultiStepForm/components/ConfirmationStep/index.tsx

Description

Notes:

The information about call-time preference is visually displayed as a description list. In this case, it's a question and then an answer. This content is not marked up as a list.

When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".

This particular list is confusing because it is displayed even when there is no time indicated, like when a user chooses email as the preferred method of communication.

Site Navigation

hashtag
Multi Step Forms

DNT-2658 Components must receive focus in an order that preserves meaning and operability

DNT-2658

Components must receive focus in an order that preserves meaning and operability

Docs

InputsType

-

-

output*

YearlyOutputProps

-

-

analytics

ComponentEvent

-

-

backgroundColor

string

-

-

bgColorClass

"""bg-gray-lighter"

-

-

ctaText

any

-

-

fullWidth

boolean

-

-

image

any

-

-

isCtaAButton

boolean

-

-

link

any

-

-

mobileLink

any

-

-

modal

{ id: string; url?: string; } | null

-

-

subtitle

any

-

-

title

any

-

-

file-archive
4KB
EVCalculator.zip
archive
arrow-up-right-from-squareOpen
file-archive
8KB
Calculator.zip
archive
arrow-up-right-from-squareOpen
//Calculator/index.tsx

import React from "react";
import Input from "./inputComponents/Input";
import Stepper from "./inputComponents/Stepper";
import Slider from "./inputComponents/Slider";

const Calculator = () => (
  <section className="px-16 md:px-24 py-32 md:py-48">
    <div className="mb-48 items-center flex">
      <Slider
        prompt="Prompt goes here"
        defaultVal="88888"
        min="0"
        max="100000"
        label="Number Slider Title"
      />
    </div>
    <div className="flex flex-1">
      <Stepper prompt="Prompt goes here" defaultVal="1" />
      <Input
        prompt="Prompt goes here"
        defaultVal="500"
        label="per month"
        isInDollars={true}
      />
    </div>
  </section>
);

export default Calculator;
//src/components/Calculator/outputComponents/YearlySavings.tsx

import { YearlyOutputProps } from "../types";

const avgDaysInMonth = 30.437;
const avgDaysInYear = 365.25;

const formatMoney = (money: number) => {
  const roundedNum = Math.round(money * 100) / 100;
  const arr = roundedNum.toLocaleString().split(".");
  const cents = `${arr[1] || ""}00`.slice(0, 2);
  const dollars = arr[0];
  return {
    dollars,
    cents,
    full: `$${dollars}.${cents}`,
  };
};

const YearlySavings = ({
  dailySavings = 0,
  headline = "Total Yearly Savings",
  monthlyText = "Monthly Savings",
  dailyText = "Daily Savings",
  description,
}: YearlyOutputProps) => {
  const annual = formatMoney(dailySavings * avgDaysInYear);
  const monthly = formatMoney(dailySavings * avgDaysInMonth);
  const daily = formatMoney(dailySavings);

  return (
    <div>
      <h4 className="text-blue text-xl-fixed">{headline}</h4>
      {/* need to adjust line height to vertically top align */}
      <p
        className="text-blue my-12 text-2xl-fixed align-top"
        style={{ lineHeight: "55px" }}
      >
        $
        <span className="text-3xl-fixed align-top leading-none">
          {annual.dollars}
        </span>
        {`.${annual.cents}`}
      </p>
      <div className="flex flex-row justify-center">
        <div className="border-r px-32 border-gray">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{daily.dollars}</span>
            {`.${daily.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{dailyText}</p>
        </div>
        <div className="px-32">
          <p
            className="text-blue align-top text-lg-fixed"
            style={{ lineHeight: "25px" }}
          >
            $<span className="text-xl-fixed align-top">{monthly.dollars}</span>
            {`.${monthly.cents}`}
          </p>
          <p className="text-gray-dark text-xs">{monthlyText}</p>
        </div>
      </div>
      {description && (
        <p className="mt-32 text-gray-dark text-md">{description}</p>
      )}
    </div>
  );
};

export default YearlySavings;
//src/components/Calculator/test.tsx

import "@testing-library/jest-dom";
import Input from "src/components/Calculator/inputComponents/Input";
import Slider from "src/components/Calculator/inputComponents/Slider";
import YearlySavings from "./outputComponents/YearlySavings";
import { renderWithCTX, screen, fireEvent } from "src/lib/testWrappers";

const props: Parameters<typeof Input>[0] = {
  prompt: "Current Gas Price",
  defaultVal: "3.12",
  label: "per gallon",
  isInDollars: true,
};

describe("calculator input", () => {
  it("should render", () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole("textbox", {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute("value", props.defaultVal);
  });
  it("should prefix with $ if applicable", () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe("calculator slider input", () => {
  const props = {
    prompt: "How many miles do you drive daily?",
    defaultVal: "160",
    label: "Daily Miles",
    min: "0",
    max: "320",
    step: "1",
  };
  it("should render", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole("heading", {
      name: /how many miles do you drive daily\?/i,
    });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it("should default to midpoint between min and max when defaultVal is missing", () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    expect(slider).toHaveAttribute("value", "200");
  });
  it("should keep the same slider and text input values", () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole("slider", {
      name: `${props.prompt} ${props.label}`,
    });
    const input = screen.getByRole("textbox", {
      name: `${props.prompt} ${props.label}`,
    });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute("value", "28");
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute("value", "140");
  });
});

describe("yearly savings output", () => {
  const props = {
    headline: "Yearly Savings",
    monthlyText: "Monthly Savings",
    dailyText: "Daily Savings",
    dailySavings: 2.22,
  };
  it("should render", () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole("heading", { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it("should properly display rounded monetary amounts", () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText("8");
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});
// Stepper Test.tsx



import '@testing-library/jest-dom';
import Input from 'src/components/Calculator/inputComponents/Input';
import Slider from 'src/components/Calculator/inputComponents/Slider';
import YearlySavings from './outputComponents/YearlySavings';
import { renderWithCTX, screen, fireEvent } from 'src/lib/testWrappers';

const props: Parameters<typeof Input>[0] = {
  prompt: 'Current Gas Price',
  defaultVal: '3.12',
  label: 'per gallon',
  isInDollars: true,
};

describe('calculator input', () => {
  it('should render', () => {
    renderWithCTX(<Input {...props} />);
    const input = screen.getByRole('textbox', {
      name: `${props?.prompt} ${props?.label}`,
    });
    expect(input).toBeInTheDocument();
    expect(input).toHaveAttribute('value', props.defaultVal);
  });
  it('should prefix with $ if applicable', () => {
    const { rerender } = renderWithCTX(<Input {...props} />);
    let currency: HTMLElement | null = screen.getByText(/\$/i);
    expect(currency).toBeInTheDocument();
    rerender(<Input {...props} isInDollars={false} />);
    currency = screen.queryByText(/\$/i);
    expect(currency).not.toBeInTheDocument();
  });
});

describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});



describe('calculator slider input', () => {
  const props = {
    prompt: 'How many miles do you drive daily?',
    defaultVal: '160',
    label: 'Daily Miles',
    min: '0',
    max: '320',
    step: '1',
  };
  it('should render', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    const label = screen.getByText(/daily miles/i);
    const heading = screen.getByRole('heading', { name: /how many miles do you drive daily\?/i });
    expect(slider).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(label).toBeInTheDocument();
    expect(heading).toBeInTheDocument();
  });
  it('should default to midpoint between min and max when defaultVal is missing', () => {
    renderWithCTX(<Slider {...props} defaultVal="" min="100" max="300" />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    expect(slider).toHaveAttribute('value', '200');
  });
  it('should keep the same slider and text input values', () => {
    renderWithCTX(<Slider {...props} />);
    const slider = screen.getByRole('slider', { name: `${props.prompt} ${props.label}` });
    const input = screen.getByRole('textbox', { name: `${props.prompt} ${props.label}` });
    // slider changes input
    fireEvent.change(slider, { target: { value: 28 } });
    expect(input).toHaveAttribute('value', '28');
    // input changes slider
    fireEvent.change(input, { target: { value: 140 } });
    expect(slider).toHaveAttribute('value', '140');
  });
});

describe('yearly savings output', () => {
  const props = {
    headline: 'Yearly Savings',
    monthlyText: 'Monthly Savings',
    dailyText: 'Daily Savings',
    dailySavings: 2.22,
  };
  it('should render', () => {
    renderWithCTX(<YearlySavings {...props} />);
    const heading = screen.getByRole('heading', { name: props.headline });
    const daily = screen.getByText(props.dailyText);
    const monthly = screen.getByText(props.monthlyText);
    expect(heading).toBeInTheDocument();
    expect(daily).toBeInTheDocument();
    expect(monthly).toBeInTheDocument();
  });
  it('should properly display rounded monetary amounts', () => {
    renderWithCTX(<YearlySavings dailySavings={7.998} />);
    const dollars = screen.getByText('8');
    const cents = screen.getByText(/\$\.00/i);
    expect(dollars).toBeInTheDocument();
    expect(cents).toBeInTheDocument();
  });
});

i.e. input modulo 100 should return a number not equal to input.

Abstractarrow-up-right

Additionally the calculator should include an explanation of the fictional unit being used in the output

i.e.

When you participate in the GoGreen Ohio program, you’re supporting the advancement of green power sources. Green power is electricity produced from natural resources – like the sun, wind, and water – that do not emit pollutants into the atmosphere.

As a GoGreen Ohio participant, you can match your home’s electricity use by purchasing blocks* of renewable energy certificates (RECs). These RECs certify the generation of green power on your behalf. Every block you purchase supports the advancement of environmentally friendly, renewable energy sources, thereby reducing dependence on fossil fuels to generate energy.

*One GoGreen Ohio program block represents 100 kWh of clean energy. A 10-block purchase (1,000 kWh) equals one REC (1MWh).

This will be a replacement of the existing calculators: Go Green: https://www.duke-energy.com/home/products/renewable-energy/gogreen-energyarrow-up-right (Ohio jurisdiction, calc is under the Go Green Ohio video)

Renewable Advantage: https://www.duke-energy.com/home/products/renewable-advantagearrow-up-right (NC jurisdiction, look for "Calculate Your Estimated Cost"

    • Design questions contact: marcus.wilson@duke-energy.comenvelope\

https://app.abstract.com/projects/7d33aa49-f1f0-47eb-971d-893d6457bcbc/branches/4d491d6e-63f5-4bad-951e-0f327cfc047c/commits/afba0d4f418ba1a5f186815496c8331add993f74/files/56AED96A-C786-42FD-8B75-CFA17F1BE644/layers/BB077CE9-8557-4DDA-8C01-FC5575EE35A4?collectionId=123e3638-3117-4bbb-b7b0-8e43052397a8&collectionLayerId=6d234c60-58b2-4027-a631-e01d158248abarrow-up-right

This will be a replacement of the existing calculators:

Go Green: https://www.duke-energy.com/home/products/renewable-energy/gogreen-energyarrow-up-right (Ohio jurisdiction, calc is under the Go Green Ohio video)

Renewable Advantage: https://www.duke-energy.com/home/products/renewable-advantagearrow-up-right (NC jurisdiction, look for "Calculate Your Estimated Cost"

Design questions contact: Marcus Wilson

SimpleCalc-Min-Val
Go-Green(OH)
Renewable-Advantage(NK)

MatchElectricUsage

200kWh

250kWh

"The minimum kWh that can be entered is"

ChooseYourPayment

$2.00

$3.00

"The minimum amount that can be entered is $"

Simple Calc Brancharrow-up-right
📖 Storybook

Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

To get it up and running navigate to the app repo and, from the command line, enter:

Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

hashtag
Stories

A story, much like a React component, is a function that describes how to render a component.

You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

So here's how the initial default PushDownPanel might look:

And then we can add variations based on different states, for example a version that uses icons:

And the new stories will show up in the sidebar navigation, like so:

hashtag
Args

Story definitions can be further improved to take advantage of Storybook's "args" concept.

A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

hashtag
Controls

Controls allow designers and developers to explore component behavior by mucking about with its arguments.

Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

A select menu to control the number of items that render:

And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

hashtag
A11y and Other Addons

Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

hashtag
Anatomy of a Story

Stories exist alongside the other component files as stories.js.

Here's an example of how a typical story might take shape:

Let's break this down line by line:

Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

hashtag
storybookCustomArgs

The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

hashtag
createStoryOptions

Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

hashtag
Resources

  • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

  • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

  • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

  • [Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

Self Link

https: //duke-2.gitbook.io/duke

https://duke-3.gitbook.io/duke/arrow-up-right

hashtag
Duke Training:

query
searches
hits
pageHits
sectionHits

nav

1

155

2

153

stepper

1

4

Website Navigation


hashtag
Table of contents

hashtag
General Info

hashtag
Sitecore

arrow-up-right
🎟️Jira Ticketschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/jira-tickets/dnt-2658%20(1).mdchevron-right
🎫DNT-2659 A meaningful image must have a text alternative that serves the equivalent purposechevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/jira-tickets/dnt-2658-1.mdchevron-right
🧪DNT-2724 Accordion Testingchevron-right
⛔2654-a11y-Audit-Form-Errorschevron-right
🤷Personalchevron-right
📚Docschevron-right
🧱Component Creationchevron-right
Analyticschevron-right
✅DefinitionOfDonechevron-right
🤖TechnicalOverviewchevron-right
🧑‍🏫🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverviewchevron-right
📚Sitecorechevron-right
👨‍⚕️👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Introchevron-right
🚧Setupchevron-right
📚Docschevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
🌀Tailwind CSSchevron-right
📖Storybookchevron-right
📼Typescriptchevron-right
💡Ideaschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/broken-reference/README.mdchevron-right
🦿ACCESSIBILITYchevron-right
Ariachevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/ke-energy.mdchevron-right
🗺️Site Navigationchevron-right
🎟️Jira Ticketschevron-right
🛑DNT-2658chevron-right
🎫DNT-2659 A meaningful image must have a text alternative that serves the equivalent purposechevron-right
🧪DNT-2724 Accordion Testingchevron-right
⛔2654-a11y-Audit-Form-Errorschevron-right
🤷Personalchevron-right
📚Docschevron-right
🧱Component Creationchevron-right
Analyticschevron-right
✅DefinitionOfDonechevron-right
🤖TechnicalOverviewchevron-right
🧑‍🏫🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverviewchevron-right
📚Sitecorechevron-right
👨‍⚕️👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Introchevron-right
🚧Setupchevron-right
📚Docschevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
🌀Tailwind CSSchevron-right
📖Storybookchevron-right
📼Typescriptchevron-right
💡Ideaschevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/broken-reference/README.mdchevron-right
🦿ACCESSIBILITYchevron-right
Ariachevron-right
🖨️Typescriptchevron-right
Typescript Typeschevron-right
Types VS Interfaceschevron-right
Typescript Interfaceschevron-right
Enumschevron-right
Types VS Interfaceschevron-right
Typescript Rules:chevron-right
🦽Accessabilitychevron-right
Focus Orderchevron-right
🦾ARIA - Accessibilitychevron-right
⚙️Testingchevron-right
🎹Testing Inputchevron-right
🧪React Testing Librarychevron-right
⚒️Most Usefulchevron-right
🌪️Tailwind Cheatsheetchevron-right
🔖Bookmarkschevron-right
🖥️Websitechevron-right
😀Overviewchevron-right
Duke Energy Manual Audit_Reportchevron-right
Live Deploychevron-right
⚒️CREATING COMPONENTSchevron-right
ℹ️Introchevron-right
🧑💻 TechnicalOverviewchevron-right
👨‍🔬👨🔬 👨🔬 👨🔬 👨🔬 👨🔬 👨🔬 🔬 Practical Overviewchevron-right
📈Analyticschevron-right
📰Sitecorechevron-right
Sitecore-Docschevron-right
✅DefinitionOfDonechevron-right
⚙️SETUPchevron-right
🧪Testingchevron-right
ℹ️Formschevron-right
⚛️Reactchevron-right
📖Storybookchevron-right
📔Official Tutorialchevron-right
🧱Component Driven Designchevron-right
🗒️Code Splittingchevron-right
🖼️SvgLoaderchevron-right
🌬️Tailwind CSSchevron-right
💯Unit Testschevron-right
📚General Infochevron-right
⚛️REACT NOTESchevron-right
Hooks API Reference - Reactchevron-right
Using the Effect Hook - Reactchevron-right
Getting Started With Reactchevron-right
React ToDoListchevron-right
Componentizing our React appchevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/react-interactivity-events-and-state.mdchevron-right
https://github.com/bgoonz/DUKE/blob/ghpages/react-interactivity-editing-filtering-conditional-rendering.mdchevron-right
Create React Appchevron-right
React Componentschevron-right
Handling Eventschevron-right
React Testing Recipes:chevron-right
🕸️Webpackchevron-right
🥘Curry VS Functional Compositionchevron-right
🛗Argvchevron-right
💬Bubbling & Capturingchevron-right
🔲Moduleschevron-right
Private NPM Packageschevron-right
aria-labelledby - Accessibilitychevron-right
Focuschevron-right
⁉️Optional Chaningchevron-right
💍Promiseschevron-right
🔢Enums In Javascriptchevron-right
🗣️JIRAchevron-right
🥑Airbnb JavaScript Style Guidechevron-right
🤸‍♂️🤸♂ 🤸♂ 🤸♂ 🤸♂ 🤸♂ 🤸♂ Performancechevron-right
🎛️Sitecorechevron-right
😁DXT-Solutionchevron-right
💻Codechevron-right
Index.tsxchevron-right
Composition.tsxchevron-right
Data.jschevron-right
Types.tschevron-right
Stories.jschevron-right
Test.tsxchevron-right
🕐Testing Assignmentchevron-right
🗓️Week 1chevron-right
🗓️Weekschevron-right
📅Day 4chevron-right
Day 2chevron-right
Day 3chevron-right
🗓️Week 2chevron-right
🗓️Week 3chevron-right
Day 1chevron-right
Day 5chevron-right
Day 4chevron-right
Day 2chevron-right
Day 3chevron-right
🗓️Week 4chevron-right
🗓️Week 5chevron-right

Recommendation

Remove the list entirely and write this information as a sentence. "We will reach out to you by phone during business hours," for example. If the user indicates that they prefer email, either write a new sentence indicating that preference, "One of our representatives will email you shortly," or remove the sentence altogether.

If you choose to keep the question and answer structure, use a description list (<dl>)for this data.

Code example (simplified)

Resources

Using description listsarrow-up-right

chevron-rightDescription Listshashtag

Using description lists

Important Information about Techniques

See Understanding Techniques for WCAG Success Criteriaarrow-up-right for important information about the usage of these informative techniques and how they relate to the normative WCAG 2.1 success criteria. The Applicability section explains the scope of the technique, and the presence of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1.

Applicability

HTML and XHTML

This technique relates to (Sufficient when used with ).

Description

The objective of this technique is to provide the description of names or terms by presenting them in a description list. The list is marked up using the dl element. Within the list, each term is put in a separate dt element, and its description goes in the dd element directly following it. Multiple terms can be associated with a single description, as can a single term with multiple descriptions, provided that semantic sequence is maintained. The title attribute can be used to provide additional information about the description list. Usage of description lists ensures that terms and their descriptions are semantically related even as presentation format changes, as well as ensuring that these terms and descriptions are semantically grouped as a unit.

Description lists are easiest to use when the descriptions are ordered alphabetically. A common use for description lists is a glossary of terms.

NOTE

In HTML5 the name "Description list" was introduced. In previous versions these lists were referred to as "Definition lists".

Examples

Example 1

A list of descriptions of nautical terms used on a Website about sailing.

Resources

Resources are for information purposes only, no endorsement implied.

Related Techniques

Tests

Procedure

For any set of terms and their associated descriptions:

  1. Check that the list is contained within a dl element.

  2. Check that each term in the list being described is contained within a dt element.

  3. Check that when there is more than one term that shares the same decription that the dt

Expected Results

  • All checks above are true.

Yes-Account

/Home/Billing/Prepaid-Advantage/Enroll/Content/Yes-Account

MultiStepFormBuilder

/Home/Billing/Prepaid-Advantage/Enroll/No/Content/MultiStepFormBuilder

Yes-Account

/Home/Billing/Prepaid-Advantage/Enroll/Yes/Content/Yes-Account

thirdPartyMultiStep

/Home/Billing/Special-Assistance/Third-Party-Notification/Third-Party-Notify-Enroll/Content/thirdPartyMultiStep

Medical-Certificate-Request-Form

/Home/Billing/Special-Assistance/Medical-Certificate-Request/Content/Medical-Certificate-Request-Form

Move-Common-REMOVED-R5

/Home/Start-Stop-Move/Move/Content/Move-Common-REMOVED-R5

MultiStepFormMove

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move/Content/MultiStepFormMove-Rework

Move-Common-FL-REMOVED-R5

/Home/Start-Stop-Move/Move-DEP-FL/Content/Move-Common-FL-REMOVED-R5

Move-Common-REMOVED-R5

/Home/Start-Stop-Move/Move-DEP-FL/Content/Move-Common-REMOVED-R5

MultiStepFormMove

/Home/Start-Stop-Move/Move-DEP-FL/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move-DEP-FL/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move-IN/Content/MultiStepFormMove-Rework

MultiStepFormMove

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove

MultiStepFormMove-COPY

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Home/Start-Stop-Move/Move-MW/Content/MultiStepFormMove-Rework

MultiStepFormREWORK

/Home/Start-Stop-Move/Start/Content/MultiStepFormREWORK

Start-All-Juris-DEC

/Home/Start-Stop-Move/Start/Content/Start-All-Juris-DEC

Start-All-Juris-FL

/Home/Start-Stop-Move/Start/Content/Start-All-Juris-FL

Stop-Service

/Home/Start-Stop-Move/Stop/Content/Stop-Service

Stop-Service-DEC

/Home/Start-Stop-Move/Stop/Content/Stop-Service-DEC

Stop-Service-MW-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-MW-not-used

Stop-Service-DEP-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-DEP-not-used

Stop-Service-FL-not-used

/Home/Start-Stop-Move/Stop/Content/Stop-Service-FL-not-used

Stop-Service-FL

/Home/Start-Stop-Move/Stop/Content/Stop-Service-FL

Stop-Service-nonDEC

/Home/Start-Stop-Move/Stop/Content/Stop-Service-nonDEC

Stop-Service-COPY

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-COPY

Stop-Service-REWORK

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-REWORK

Stop-Service-REWORK-BU

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-REWORK-BU

Stop-Service-ALL-JUR-old

/Home/Start-Stop-Move/Stop/Content/old/Stop-Service-ALL-JUR-old

MultiStepFormBuilder

/Home/Start-Stop-Move/Request-a-Reference-Letter/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Home/Products/Renewable-Advantage/Enroll/Content/MultiStepFormBuilder

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-Carolinas/Content/Enroll-in-Power-Manager-Form

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-OH-KY/Content/Enroll-in-Power-Manager-Form

Enroll-in-Power-Manager-Form

/Home/Products/Power-Manager/Enroll-IN/Content/Enroll-in-Power-Manager-Form

Enroll-In-NC-GreenPower

/Home/Products/Renewable-Energy/NC-GreenPower/Enroll-In-NC-GreenPower/Content/Enroll-In-NC-GreenPower

Enroll-Online-Indiana

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Indiana/Content/Enroll-Online-Indiana

Enroll-Online-Indiana-2021

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Indiana/Content/Enroll-Online-Indiana-2021

Enroll-Online-Kentucky

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Kentucky/Content/Enroll-Online-Kentucky

Enroll-Online-Ohio

/Home/Products/Renewable-Energy/GoGreen-Energy/Enroll-Online-Ohio/Content/Enroll-Online-Ohio

MultiStepFormBuilder

/Business/Billing/Automatic-Payment-Plan/Enroll/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Business/Billing/Equal-Payment-Plan/Enroll/Content/MultiStepFormBuilder

SignUpMultiStepForm

/Business/Billing/Time-of-Use-Rate/Enroll/Content/SignUpMultiStepForm

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/DEP-FL-no-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/DEP-FL-no-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/DEP-FL-with-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/DEP-FL-with-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-No-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-No-Fed/Move-Service-Indiana/Content/MultiStepFormMove-Rework

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/MW-No-Fed/Start/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/MW-No-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-With-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormMove-Rework

/Business/Start-Stop-Move/MW-With-Fed/Move-Service-IN/Content/MultiStepFormMove-Rework

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/MW-With-Fed/Start/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/MW-With-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/Non-Res-Start-Service-Request/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormBuilder-COPY

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-DEP-SC/Content/MultiStepFormREWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormBuilder-COPY

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-FL/Content/MultiStepFormREWORK

MultiStepFormREWORK

/Business/Start-Stop-Move/Start-Service-DEP-NC/Content/MultiStepFormREWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/With-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/With-Fed/Start-IN/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/With-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-With-Fed-REWORK

/Business/Start-Stop-Move/With-Fed/Stop-Service/Content/Stop-Service-With-Fed-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormBuilder

MultiStepFormMove-COPY

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormMove-COPY

MultiStepFormMove-Rework

/Business/Start-Stop-Move/No-Fed/Move-Service/Content/MultiStepFormMove-Rework

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/No-Fed/Start-IN/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder-COPY

MultiStepFormBuilder-REWORK

/Business/Start-Stop-Move/No-Fed/Start-Service/Content/MultiStepFormBuilder-REWORK

MultiStepFormBuilder

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/MultiStepFormBuilder

MultiStepFormBuilder-COPY

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/MultiStepFormBuilder-COPY

Stop-Service-No-Fed-REWORK

/Business/Start-Stop-Move/No-Fed/Stop-Service/Content/Stop-Service-No-Fed-REWORK

MultiStepFormBuilder

/Business/Products/Renewable-Advantage/Enroll/Content/MultiStepFormBuilder

EDA-Multi-step-form-2

/Business/Products/Design-Assistance/Request/Content/EDA-Multi-step-form-2

EDAMultistepForm-COPY

/Business/Products/Design-Assistance/Request/Content/EDAMultistepForm-COPY

EnergyDesignAssistanceMultiStepForm

/Business/Products/Design-Assistance/Request/Content/EnergyDesignAssistanceMultiStepForm

Enroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Indiana-Large-Business/Enroll/Content/Enroll-MultiStepForm

Enroll-in-NC-GreenPower

/Business/Products/Renewables/NC-GreenPower/Enroll-Now/Content/Enroll-in-NC-GreenPower

Enroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Energy/Enroll-KY/Content/Enroll-MultiStepForm

Enroll-in-GoGreen-Ohio

/Business/Products/Renewables/GoGreen-Energy/Enroll-OH/Content/Enroll-in-GoGreen-Ohio

Enrroll-MultiStepForm

/Business/Products/Renewables/GoGreen-Energy/Enroll-IN/Content/Enrroll-MultiStepForm

MultiStepFormBuilderNEW

/Home-Services/Strikestop/enroll/Content/MultiStepFormBuilderNEW

Renew-Strikestop

/Home-Services/Strikestop-Renew/Enroll/Content/Renew-Strikestop

Travel-Request-Form

/Our-Company/Careers/Accommodations/Travel-Form/Content/Travel-Request-Form

MultiStepFormBuilder

/Customer-Service/Email-Us/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Customer-Service/Email-Us-FYB/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Customer-Service/Request-Tree-Trimming/Content/MultiStepFormBuilder

MultiStepFormBuilderNEW

/Spanish/Servicios-al-Hogar/StrikeStop/Inscripcion/Content/MultiStepFormBuilderNEW

MultiStepFormBuilder-Request

/Spanish/Servicio-al-Cliente/TEST-Request-Tree-Trimming/Content/MultiStepFormBuilder-Request

MultiStepFormBuilder

/Test/testmulti/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/Components/MultiStepForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/MultiStepForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Automation/MultiStepPIForm/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/CI-Test/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder

MultiStepFormBuilder-2

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder-2

MultiStepFormBuilder-3

/Test/CI-Test/Test-Form/Content/MultiStepFormBuilder-3

Enroll-in-Automatic-Payment-Form

/Test/Enroll/Content/Enroll-in-Automatic-Payment-Form

Landlord-Advantage

/Test/Landlord-Advantage-Form/Content/Landlord-Advantage

MultiStepFormBuilder

/Test/Validation/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Validation/584/Content/MultiStepFormBuilder

MultiStepFormBuilder

/Test/Validation/Form-Multi/Content/MultiStepFormBuilder

Multi-Step-Form

/JSSMigration/Multi-Step-Form/Content/Multi-Step-Form

Start-All-Juris-NonDEC

/sitecore/content/SSM/Home/Content/Start-All-Juris-NonDEC

PropertyManagerFormNew

/sitecore/content/SSM/Home/Property-Managers-Requests/Content/PropertyManagerFormNew

hashtag
Related Links

Name
Updated
Updated by
Created
Created by
Path

Related Links

12/12/2017 8:11:48 AM

nam\jholly1

3/16/2017 3:04:16 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/HealthCheck/Content/Related Links

Related Links

chevron-righthashtag

Name

Path

MultiStepFormBuilder

/Home/HealthCheck/Form-Multi/Content/MultiStepFormBuilder

No-Account

/Home/Billing/Prepaid-Advantage/Enroll/Content/No-Account

DNT-2930 Hero Component

hashtag
Hero

Features a full-width image (light or dark) with superimposed text (light or dark contrasting). See a demo pagearrow-up-right with a site header.

hashtag
Usage Guidance

Do

  • Use on home pages and landing pages with multiple child pages.

  • When used on pages other than Home Pages or Section Landing Pages, the headline (H1) must be the page title.

  • Include a title/headline (required), a description (optional), and call-to-action link button(s) (optional).

Do Not

  • Use more than ONE Hero on a page.

Toggle Component Options

Theme Black to Gray-Darker Blue-Dark to Blue Blue to Teal-Darker Green-Dark to Green Blue-Dark to Green-Dark Green-Dark to Blue-Dark Gray-Light to white Gray-Light to Gray-Lighter Teal-Light to Teal-Lighter Image Business Solar Chef Family Landscape One Person Store Solar Solar Cell Two Person Store Content Content 1 Content 2 Content 3 Too Much Content Action No Actions Primary Action Primary and Secondary Action

hashtag
Say Yes to Renewables

Striving for sustainability is just smart business

hashtag
Accessibility

Labeling Expectations

  • Use a unique `id=""` on the title.

  • Use a unique `id=""` on the action link button(s).

  • Use `aria-labelledby=""` on the link buttons referencing the id of the link button(s) and the id of the title.

Keyboard Expectations

  • When you TAB into the `Hero` component, focus will go onto the link button(s).

Resources

DNT-2423 Throttle scroll event listeners

    // TODO Throttle event listeners
    window.addEventListener('scroll', handleScroll, { passive: true });

    return () => {
      window.removeEventListener('scroll', handleScroll);
    };
  }, []);

  if (suppressNav || !hasSubPages) {
    return null;
  }

See: src/components/SecondaryNav/index.tsx - L:99

We probably don't need event fired for every single scroll.

chevron-rightSecondary Navhashtag
/* eslint-disable complexity */
/* eslint-disable max-lines */
import React

Throtteling Event Listeners

chevron-rightThrottlehashtag

Using Throttling and Debouncing with React hooks - DEV Community

Excerpt

Throttling and debouncing techniques has been in use for past many years in javascript. In this post I'd like to share my knowledge on how we can use throttle and debounce functions with help of react hooks.

Consider below example with two routes / and /count rendering respective components.

Throttling Example with useEffect

Suppose we need to subscribe a scroll event on Count component on its mount and just increment the count on every scroll event.

Code without using throttle or debounce techniques will be like:

Suppose in practical applications you need to use throttle and wait for every 100ms before we execute increaseCount. I have used the lodash throttle function for this example.

Wait, no need to hurry. It will work if you are at /count route. The increaseCount function will be throttled and will increase the count after 100ms of intervals.

But as you move to the / route to render the Home component and unmount the Count component, and start scrolling on home page, you will notice a warning in console which warns about memory leak. This is probably because the scroll event was not cleaned properly. The reason is _.throttle(increaseCount, 100) is called again during unmount and returns another function which does not match that created during the mount stage. What if we create a variable and store the throttled instance.

like this

But it has problem too. The throttledCount is created on every render, which is not at all required. This function should be initiated once which is possible inside the useEffect hook. As it will now be computed only once during mount.

Debounce Example using useCallback or useRef

Above example is pretty simple. Let's look at another example where there is an input field and you need to increment the count only after user stops typing for certain time. And there is text which is updated on every keystroke which re renders the component on every input.

Code with debounce:

This will not work. The count will increase for every keystroke. The reason behind is that on every render, a new debouncedCount is created. We have to store this debounced function such that it is initiated only once like that in useEffect in above example. Here comes use of useCallback. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed - React docs Replace

with

and it will work. Because this time the function is evaluated only once at the initial phase.

Or we can also use useRef by doing this

One should always keep in mind that every render call of react functional component will lead to expiration of local variables and re-initiation unless you memoize them using hooks.

DNT-2785

/info/unindexed/dpa-conversion-business

The Image Slideshow component seems to have shrunk after JSS conversion. See it here: https://scprod-cms.duke-energy.com/info/unindexed/dpa-conversion-businessarrow-up-right, compared to pre-JSS: https://scdev28.duke-energy.com/info/unindexed/dpa-conversion-businessarrow-up-right. As you can see, it cuts off some of the image space and the description underneath is completely gone.

chevron-rightModal.tsxhashtag
import React, { useRef, PropsWithChildren } from 'react';
import { createPortal } from 'react-dom';
import { a11yAction } from 'src/lib/helpers';
import { Placeholder } from '@sitecore-jss/sitecore-jss-react';
import SvgLoader from 'src/components/SvgLoader';
import Transition from 'src/lib/Transition';
import { useBodyContext } from 'src/components/ContentWrapper/context';
import { useExperienceEditor } from 'src/lib/useExperienceEditor';
import useMediaQuery from 'src/lib/useMediaQuery';
import usePreventBodyScroll from 'src/lib/usePreventBodyScroll';
import { ModalComponentTypes, ModalTypes } from './types';
import { useFocusTrap } from 'src/lib/useFocusTrap';

Code Review:

hashtag

  • Created by Anonymous, last modified on

"Everybody is junior at something", or so they say. You may be fairly early on in your career but have some experience working in a large enterprise or business where structured Code Reviews were the order of the day. Or you may have many years experience on a loose team that didn't really take Code Review very seriously. Thus, you may find that while you are a senior developer, you are "junior" (I honestly hate that term) when it comes to Code Reviews, and vice versa. Whatever the case, Ninja Turtles values Code Review.

DNT-2659 A meaningful image must have a text alternative that serves the equivalent purpose

chevron-rightInfohashtag

Duke Company

hashtag
Portal Guide - Home

Excerpt


The Modern Portal Project Is Complete! »

Personal

chevron-rightTwo External Monitors (Macbook Air)hashtag

Hi, I have a macbook air... is it at all possible for me to find a workaround that allows f

Incident ID: INC000032293420 Last Modified Date: 11 Mar 2022 18:59:06 UTC Assignee:

Hi ,

DNT-2724 Accordion Testing

chevron-right5 Things You Didn't Know About React Testing Libraryhashtag

Excerpt

I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"


I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"

npm run storybook
export const NoIcons = Template.bind();
NoIcons.args = {
  items: itemsOptions["3"],
};
export const Icons = Template.bind({});
Icons.args = {
  items: itemsOptions["5"],
};
export default {
  title: "Components/Accordion",
  component: AccordionComponent,
  argTypes: {
    theme: {
      name: "Theme",
    },
    closeOthers: {
      name: "One Panel Open At A Time",
    },
  },
};

const Template = (args) => <AccordionComponent {...args} />;

export const Primary = Template.bind({});

Primary.args = {
  ...props,
};
argTypes: {
  backgroundColor: {
    control: {
      type: 'radio',
      options: ['white', 'gray'],
    },
    defaultValue: 'white',
  },
},
argTypes: {
  items: {
    name: 'How many items?',
    control: {
      type: 'select',
      options: {
        ...itemsPrimary,
      },
    },
  },
},
argTypes: {
  title: {
    control: {
      type: 'text',
    },
  },
  subtitle: {
    control: {
      type: 'text',
    },
  },
  link: {
    control: 'boolean',
  },
  ctaType: {
    control: {
      type: 'inline-radio',
      options: ['ImageSlide', 'VideoSlide'],
    },
  },
},
import React from "react";
import MyComponent from "./index";
import { Data } from "./data";
import { MyComponent as MyComponentComposition } from "../../lib/composition";

const props = MyComponentComposition({ fields: Data });

export default {
  title: "Components/MyComponent",
  component: MyComponent,
  argTypes: {
    backgroundColor: {
      control: {
        type: "radio",
        options: ["white", "gray"],
      },
      defaultValue: "white",
    },
  },
};

const Template = (args) => <MyComponent {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  ...props,
};
import React from 'react';
import MyComponent from './index';
import { Data } from './data';

...
...

import { MyComponent as MyComponentComposition } from '../../lib/composition';


const props = MyComponentComposition({ fields: Data });

...
...

export default {
  title: 'Components/MyComponent',
  component: MyComponent,
  argTypes: {
    backgroundColor: {
      control: {
        type: 'radio',
        options: ['white', 'gray'],
      },
      defaultValue: 'white',
    },
  },
};

...
...


const Template = args => <MyComponent {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  ...props,
};
...

import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';


const props = BulletedOverviewComposition(Data);

const itemsToShow = createStoryOptions(props.items);

export default {
  title: 'Components/Bulleted Overview',
  component: BulletedOverview,
  argTypes: {
    ...storybookCustomArgs(props, BulletedOverview, [], false),
    items: {
      name: 'How many items?',
      control: {
        type: 'select',
        options: {
          ...itemsToShow,
        },
      },
    },
  },
};

...
<dl>
    <dt>If contact by phone is your preference, what time of day is best?</dt>
    <dd>Daytime</dd>
</dl>

7/15/2016 2:16:34 PM

NAM\SHaberm

6/6/2016 3:23:06 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Billing/Automatic Payment Plan/Content/Related Links

Related Links

11/8/2018 9:48:25 AM

nam\rvp0696

11/6/2018 10:24:55 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DE Carolinas Rates/Content/Main content container/Related Links

Related Links

11/8/2018 9:46:03 AM

nam\rvp0696

11/6/2018 10:24:55 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DE Progress Rates/Content/Main Content Container DEP SC/Related Links

Related Links

9/30/2019 3:50:08 PM

nam\rvp0696

9/27/2019 11:02:18 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DEC NC Rate Case 2019/Content/Main Content Container/Related Links

Related Links

7/21/2020 1:54:05 PM

sitecore\courtney.icenhour

7/21/2020 1:54:00 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Billing/DEI 2019 Rate Case/Content/Main Content Container/Related Links

Related Links

10/30/2019 1:17:29 PM

nam\rvp0696

9/27/2019 11:02:18 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/DEP NC Rate Case 2019/Content/Main Content Container/Related Links

Related Links

9/1/2017 2:19:00 PM

NAM\CBIcenh

8/29/2017 3:20:15 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Billing/Kentucky Rate Case/Content/Multi Column/Related Links

Related Links

1/30/2020 7:47:49 PM

sitecore\Van.Parker

1/30/2020 7:47:47 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Billing/Kentucky Rate Review/Content/Main Copy Container/Related Links

Related Links

8/25/2017 9:50:47 AM

NAM\CBIcenh

8/1/2017 11:03:23 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Billing/North Carolina DEC Rate Case/Content/Multi Column/Related Links

Related Links

9/27/2017 3:08:20 PM

nam\rvp0696

9/27/2017 2:15:28 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Billing/North Carolina Rate Case/Content/Multi Column/Related Links

Related Links

3/22/2021 3:31:13 PM

sitecore\janet.kosoglov

9/1/2016 1:54:17 PM

NAM\LBorder

/sitecore/content/JssPublic/Home/Home/Billing/Rates/Electric Fuel Adjustments/Content/Related Links

Related Links

7/18/2016 10:28:11 AM

NAM\JWells4

7/6/2016 1:07:17 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/South Carolina Rate Case/Content/Related Links

Related Links

8/17/2016 2:58:55 PM

nam\tmitch3

8/11/2016 5:04:38 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Assistance Agencies/Content/Multi Column/Related Links

Related Links DEC

4/5/2021 8:07:57 PM

sitecore\janet.kosoglov

4/5/2021 7:52:13 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Medical Alert/Content/MultiColumn/Related Links DEC

Related Links DEP

4/5/2021 7:52:35 PM

sitecore\janet.kosoglov

7/11/2016 12:48:38 PM

NAM\cball

/sitecore/content/JssPublic/Home/Home/Billing/Special Assistance/Medical Alert/Content/MultiColumn/Related Links DEP

Related Links

10/15/2021 3:05:47 PM

sitecore\jerry.wells

10/15/2021 3:05:44 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Content/MultiColumn/Related Links

Related Links

10/15/2021 3:06:19 PM

sitecore\jerry.wells

10/15/2021 3:06:16 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/How It Works/Content/Multi Column/Related Links

Related Links

6/1/2021 7:45:29 PM

sitecore\janet.kosoglov

6/17/2016 12:08:29 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Energy Saving Tips/Content/Multi Column/Related Links

Related Links

7/19/2016 1:51:33 PM

NAM\SHaberm

6/17/2016 12:08:29 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Billing/Time of Use/Rate Comparison/Content/Related Links

Related Links right

3/16/2022 6:57:22 PM

sitecore\courtney.icenhour

3/16/2022 6:55:35 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Start Stop Move/Account Change/Content/Multi Column main/Related Links right

Additional Information

11/16/2019 2:16:13 PM

sitecore\admin

9/13/2016 2:27:54 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Start Stop Move/Electric Service Manual/Content/Additional Information

Related Links

2/7/2022 12:08:19 AM

sitecore\van.parker

12/15/2021 9:35:29 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/IN 2021 irp stakeholder/Content/Intro Content/Related Links

Related Links

12/15/2021 9:35:33 PM

sitecore\van.parker

12/15/2021 9:35:29 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/in 2021 irp stakeholder old/Content/Intro Content Container/Related Links

Related Links

10/6/2016 10:16:44 AM

nam\jholly1

10/6/2016 10:16:40 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Income Qualified/Neighborhood Energy Saver/Content/Multi Column Neighbor/Related Links

Related Links

10/15/2020 4:41:02 PM

sitecore\van.parker

10/12/2020 7:33:59 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Home/Products/IN 2018 IRP Stakeholder/Content/Main content container/Related Links

Related Links

5/8/2017 3:33:40 PM

NAM\JWells4

4/28/2017 2:34:56 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Products/Multifamily Programs/Content/Unused/Multi Column/Related Links

Right Related Links

7/11/2021 8:30:25 PM

sitecore\van.parker

2/14/2017 9:36:59 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Products/Online Savings Store/Content/Not Used/Right Related Links

Related Links

7/11/2021 10:39:32 PM

sitecore\van.parker

4/6/2017 10:10:50 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Home/Products/Outdoor Lighting/Why Choose Us/Content/Multi Column/Related Links

Related Links NC PM Regulations

3/15/2021 3:35:12 PM

sitecore\van.parker

9/22/2016 9:28:52 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Home/Products/Power Manager/Content/Unused/Related Links NC PM Regulations

Related Links right sidebar

2/26/2018 8:10:18 AM

NAM\CBIcenh

2/20/2018 9:21:18 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC Solar Rebates/Content/Multi Column main 2018/Related Links right sidebar

Previous Related Links

8/16/2021 3:20:36 PM

sitecore\courtney.icenhour

8/16/2021 3:12:37 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC GreenPower/Content/Previous Related Links

Previous Related Links

8/16/2021 4:33:16 PM

sitecore\courtney.icenhour

8/16/2021 4:32:38 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Links

Previous Related Programs KYOHIN

8/16/2021 4:33:18 PM

sitecore\courtney.icenhour

8/16/2021 4:32:28 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Programs KYOHIN

IN Annual

9/13/2016 6:39:06 PM

nam\jholly1

9/13/2016 6:39:01 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/IN/IN Annual

KY Annual

9/13/2016 6:40:38 PM

nam\jholly1

9/13/2016 6:40:23 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/KY/KY Annual

OH Annual

9/13/2016 6:40:51 PM

nam\jholly1

9/13/2016 6:40:43 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/OH/OH Annual

Related Links

4/1/2021 1:38:35 AM

sitecore\van.parker

8/1/2016 1:51:51 PM

NAM\AValaka

/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/Generate Your Own/Content/Unused/Related Links

RL Related links

4/10/2017 9:51:20 AM

nam\rvp0696

3/3/2017 1:03:24 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/East End Replacement Project/Content/MC Main Copy/RL Related links

RL Gas Main Replacement

9/26/2016 11:14:13 AM

NAM\KMcCart

6/7/2016 4:59:35 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RL Gas Main Replacement

RLinks Proj Street List

11/16/2019 2:17:39 PM

sitecore\admin

7/7/2016 1:16:22 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RLinks Proj Street List

Related Links

3/24/2017 4:12:44 PM

nam\rvp0696

2/27/2017 12:47:26 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links

RL Gas line integrity

11/16/2019 2:17:40 PM

sitecore\admin

2/27/2017 12:42:01 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity

RL Gas line integrity

11/16/2019 2:17:40 PM

sitecore\admin

2/27/2017 12:37:49 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity

Related Links right col

1/6/2020 8:37:36 PM

sitecore\Van.Parker

12/30/2019 5:15:13 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/4th Street Replacement/Content/Main Content Container/Related Links right col

Related Links

12/16/2020 9:19:37 PM

sitecore\courtney.icenhour

1/16/2020 2:37:58 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Delhi Township Infrastructure/Content/Multi Column main/Related Links

Related Links

1/16/2020 3:52:13 PM

sitecore\Van.Parker

1/16/2020 2:37:58 PM

sitecore\Van.Parker

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Roundbottom Road/Content/Main Content Container/Related Links

Neighborhood Maps East

11/16/2019 2:54:54 PM

sitecore\admin

10/10/2019 9:59:05 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content East/Neighborhood Maps East

Neighborhood Maps West

11/16/2019 2:54:55 PM

sitecore\admin

10/10/2019 10:56:48 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content West/Neighborhood Maps West

Related Links

3/24/2021 1:26:54 PM

sitecore\van.parker

2/5/2019 11:16:47 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Line A Replacement/Content/Main Content/Related Links

Related Links

3/25/2021 1:36:48 PM

sitecore\van.parker

2/5/2019 11:17:37 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Winton Rd Replacement Project/Content/Multi Column New/Related Links

Related Links

10/29/2019 3:52:31 PM

NAM\CBIcenh

10/25/2019 12:16:29 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Boone County Pipeline/Content/MC main/Related Links

Related Links

2/17/2022 6:46:27 PM

sitecore\william.natta

2/9/2022 9:40:36 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Butler County Upgrade Project/Content/MC Body/Related Links

Related Links

3/18/2022 3:13:44 PM

sitecore\william.natta

3/11/2022 1:39:25 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Milford Replacement Project/Content/MC Body/Related Links

Neighborhood Maps West

1/14/2020 8:46:05 PM

sitecore\Van.Parker

10/10/2019 10:56:48 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Content/Map Content West/Neighborhood Maps West

Letters of Support

7/26/2017 11:23:06 AM

nam\rvp0696

7/20/2017 4:49:09 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Project Summary/Content/Multi Column/Letters of Support

Related Links pod 1 right

8/18/2016 1:35:20 PM

default\Anonymous

6/16/2016 10:55:20 AM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod 1 cleaner/Multi Column pod 1/Related Links pod 1 right

Related Links right 2

7/15/2016 2:07:53 PM

NAM\SHaberm

6/17/2016 1:20:48 PM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod item 2 more reliable/Multi Column 1/Related Links right 2

Related Links right 3

7/15/2016 2:07:56 PM

NAM\SHaberm

6/17/2016 1:46:44 PM

nam\avalaka

/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/Pod 3/Multi Column pod 3/Related Links right 3

Related Links IN OH KY

2/25/2021 3:53:21 PM

sitecore\courtney.icenhour

11/9/2016 6:08:16 PM

NAM\cball

/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links IN OH KY

Related Links DEC DEP NC SC FL

2/25/2021 3:53:25 PM

sitecore\courtney.icenhour

2/25/2021 3:47:54 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links DEC DEP NC SC FL

Related Links

11/16/2019 2:18:28 PM

sitecore\admin

8/24/2016 3:18:55 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/South Carolina Rate Case/Content/Related Links

Related Links

7/15/2016 3:15:37 PM

NAM\SHaberm

5/23/2016 4:56:23 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Business/Billing/Summary Billing/Sample Agreement/Content/Related Links

TOU Related Links

7/26/2016 1:47:39 PM

sitecore\Anonymous

5/20/2016 11:19:39 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Content/Multi Column/TOU Related Links

Related Links Right

9/2/2016 3:18:12 PM

NAM\JWells4

9/2/2016 3:18:02 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/How it Works/Content/Related Links Right

FAQ Related Links

9/2/2016 3:17:14 PM

NAM\JWells4

9/2/2016 3:17:02 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Eligibility and FAQs/Content/FAQ Related Links

Related Links right

9/2/2016 3:19:41 PM

NAM\JWells4

9/2/2016 3:19:22 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Billing/Time of Use Rate/Energy Saving Tips/Content/Energy Savings Multi Column/Related Links right

Related Links

11/16/2019 2:18:37 PM

sitecore\admin

8/16/2017 10:48:24 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Electric Fuel Adjustments/Content/Related Links

Related Links

11/16/2019 2:18:42 PM

sitecore\admin

8/24/2016 3:18:23 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Content/Related Links

Related Links

11/16/2019 2:18:43 PM

sitecore\admin

8/24/2016 3:18:38 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/How It Works/Content/Related Links

Related Links

11/16/2019 2:18:44 PM

sitecore\admin

8/24/2016 3:18:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Energy Saving Tips/Content/Related Links

Related Links

11/16/2019 2:18:45 PM

sitecore\admin

8/24/2016 3:18:43 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Billing/Rates/Residential Time of Use Rate/Rate Comparison/Content/Related Links

Additional Information

8/1/2016 3:59:29 PM

nam\RBGilbe

6/27/2016 2:36:06 PM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Business/Start Stop Move/Electric Service Manual/Content/Additional Information

Related Links

8/22/2016 3:09:15 PM

sitecore\Anonymous

8/19/2016 1:55:30 PM

nam\c81423

/sitecore/content/JssPublic/Home/Business/Products/Backup Generator Program/Content/Multi Column/Related Links

Related Links Resource Docs NC

6/24/2021 6:26:42 PM

sitecore\courtney.icenhour

6/24/2021 6:21:16 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Demand Response Automation/Content/Related Links Resource Docs NC

Related Links Resource Docs SC

6/24/2021 6:27:54 PM

sitecore\courtney.icenhour

6/24/2021 6:21:16 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Demand Response Automation/Content/Related Links Resource Docs SC

Related Links

9/11/2020 5:38:28 PM

sitecore\jerry.wells

5/25/2018 10:53:55 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/Design Assistance/Content/Multi Column/Related Links

Related Links IN

6/29/2021 7:15:08 PM

sitecore\courtney.icenhour

6/29/2021 7:05:44 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Electric Essential Use/Content/Related Links IN

Related Links KY OH

6/29/2021 7:15:13 PM

sitecore\courtney.icenhour

6/29/2021 7:05:44 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Electric Essential Use/Content/Related Links KY OH

Related Links

4/3/2017 11:26:16 AM

nam\rvp0696

3/30/2017 4:19:02 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Business/Products/Energy Assessments/Content/Related Links

Related Links sidebar right

3/5/2020 3:52:32 PM

sitecore\jerry.wells

1/10/2020 6:56:26 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/Energy Assessments/Content/Multi Column New/Related Links sidebar right

Copy of Resource Documents

9/28/2016 10:03:27 PM

NAM\cball

9/28/2016 10:03:20 PM

NAM\cball

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Content/Copy of Resource Documents

Copy of Related Links

7/15/2016 3:45:14 PM

NAM\SHaberm

5/24/2016 10:46:32 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Content/Copy of Related Links

Estimate RL

9/10/2016 7:15:39 PM

nam\jholly1

5/26/2016 9:08:51 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/EEB Custom Incentive Program/Content/Estimate RL

Resource RL

9/10/2016 7:24:14 PM

nam\jholly1

9/10/2016 7:24:05 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/EEB Custom Incentive Program/Content/Resource RL

Resource Documents

7/15/2016 3:47:58 PM

NAM\SHaberm

5/26/2016 3:41:58 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Energy Efficiency For Business/Technical Assistance/Content/Resource Documents

Learn More RL

4/29/2019 2:50:38 PM

NAM\JWells4

4/29/2019 2:18:30 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Learn More RL

Related Links DEC

6/30/2021 3:13:40 PM

sitecore\courtney.icenhour

6/30/2021 2:44:12 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Related Links DEC

Related Links DEP

6/30/2021 3:13:44 PM

sitecore\courtney.icenhour

6/30/2021 2:44:12 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/EnergyWise Business/Content/Related Links DEP

Related Links 1

7/1/2021 2:56:57 AM

sitecore\courtney.icenhour

5/24/2016 1:42:34 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Business/Products/Outdoor Lighting/Content/NOT USED/Related Links 1

Related Links

7/29/2016 10:19:04 AM

sitecore\Anonymous

6/5/2016 9:37:29 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/Outdoor Lighting/Why Choose Us/Content/Related Links

Learn More

7/1/2021 1:47:26 PM

sitecore\courtney.icenhour

7/1/2021 1:46:34 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/Power Manager for Business/Content/Multi Column/Learn More

Learn More RL DEC NC

6/16/2021 4:04:22 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL DEC NC

Learn More RL DEC SC

6/16/2021 4:04:29 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL DEC SC

Learn More RL IN

6/16/2021 4:04:12 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL IN

Learn More RL KY

6/16/2021 4:04:16 PM

sitecore\courtney.icenhour

6/16/2021 1:31:19 PM

sitecore\courtney.icenhour

/sitecore/content/JssPublic/Home/Business/Products/PowerShare/Content/Learn More RL KY

Related Links

8/1/2016 3:31:55 PM

nam\LPritch

6/28/2016 2:35:10 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/Renewables/NC GreenPower/Content/Related Links

Right Related Links

9/14/2016 9:28:01 AM

nam\jholly1

9/14/2016 9:27:55 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/NC GreenPower/Content/Content from Home/Right Related Links

IN Annual

9/13/2016 2:46:25 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/IN/IN Annual

KY Annual

9/13/2016 2:41:10 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/KY/KY Annual

OH Annual

9/13/2016 2:41:26 PM

nam\jholly1

9/13/2016 1:51:36 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Products/Renewables/GoGreen Energy/Content/OH/OH Annual

X Related Links

1/26/2022 4:36:29 AM

sitecore\courtney.icenhour

7/27/2016 8:35:59 PM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own/Content/X Related Links

Related Links

12/7/2021 12:52:33 PM

sitecore\jerry.wells

7/27/2016 8:35:59 PM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own BACKUP COPY/Content/Related Links

Related Links Application forms

2/8/2022 7:33:34 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:33:59 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:34:22 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:34:40 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 1/Content/Multi Column/Related Links Where to Apply

Related Links Application forms

2/8/2022 7:37:20 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:37:37 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:38:03 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:38:32 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 2/Content/Multi Column/Related Links Where to Apply

Related Links Application forms

2/8/2022 7:39:29 PM

sitecore\jerry.wells

2/4/2022 2:43:18 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Application forms

Related Links General Info

2/8/2022 7:39:54 PM

sitecore\jerry.wells

1/31/2022 7:55:05 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links General Info

Related Links Resources

2/8/2022 7:40:14 PM

sitecore\jerry.wells

2/4/2022 2:41:52 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Resources

Related Links Where to Apply

2/8/2022 7:40:37 PM

sitecore\jerry.wells

1/31/2022 8:01:08 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/Renewables/Generate Your Own Florida/Tier 3/Content/Multi Column/Related Links Where to Apply

Related Links 1

3/21/2020 1:23:04 AM

sitecore\jerry.wells

2/4/2020 8:35:31 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links 1

Related Links

1/17/2018 9:12:31 AM

NAM\CBIcenh

1/16/2018 10:03:08 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links

Related Links 2

1/19/2018 2:58:11 PM

NAM\CBIcenh

1/19/2018 8:34:22 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/Related Links 2

Related Links right

2/6/2020 6:08:47 PM

sitecore\Jerry.Wells

2/6/2020 6:08:42 PM

sitecore\Jerry.Wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Custom/Multi Column classic/Related Links right

Related Links NC SC IN

7/1/2021 1:37:00 PM

sitecore\jerry.wells

7/1/2021 1:13:09 PM

sitecore\jerry.wells

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Performance/Multi Column performance/Related Links NC SC IN

Related Links OH KY

1/24/2020 8:50:00 PM

sitecore\Janet.Kosoglov

1/24/2020 7:22:25 PM

sitecore\Janet.Kosoglov

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators KY/Custom OH KY/Multi Column classic OH KY/Related Links OH KY

Estimate incentives

1/3/2017 7:11:46 AM

NAM\JWells4

12/27/2016 9:07:34 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Performance/Content/Multi Column/Estimate incentives

Resources

1/3/2017 7:11:30 AM

NAM\JWells4

12/27/2016 8:58:51 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Performance/Content/Multi Column/Resources

RL Custom Incentives

11/23/2020 3:18:32 PM

sitecore\jerry.wells

12/13/2018 9:57:43 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives FL/Content/Multi Column NoCache/RL Custom Incentives

Related Links

7/15/2016 3:58:55 PM

NAM\SHaberm

5/16/2016 8:24:23 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Business/Natural Gas/Gas Main Replacement/Content/Related Links

Related Links KY

5/4/2021 9:40:45 PM

sitecore\van.parker

3/4/2021 1:23:14 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop/Strikestop Monetary Coverage/Content/Multi Column Strikestop/Related Links KY

Related Links OH

5/4/2021 9:40:45 PM

sitecore\van.parker

3/4/2021 12:53:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop/Strikestop Monetary Coverage/Content/Multi Column Strikestop/Related Links OH

Related Links KY

3/4/2021 1:23:16 AM

sitecore\janet.kosoglov

3/4/2021 1:23:14 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop Orig/Content/Multi Column Strikestop/Related Links KY

Related Links OH

3/4/2021 1:23:00 AM

sitecore\janet.kosoglov

3/4/2021 12:53:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Home Services/Strikestop Orig/Content/Multi Column Strikestop/Related Links OH

Related Links

3/19/2021 5:53:36 PM

sitecore\janet.kosoglov

5/17/2016 3:13:00 PM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Home Services/Underground Protection/Content/Related Links

Related links

4/8/2021 1:38:17 PM

sitecore\van.parker

3/28/2021 10:35:01 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/IRP DEK/Content/Related links

Right Related Links

5/22/2017 1:38:27 PM

NAM\JWells4

5/22/2017 9:46:04 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Businesses/Renewable Energy/Content/Multi Column Renewable Energy/Right Related Links

Related Links

7/15/2016 4:19:27 PM

NAM\SHaberm

4/28/2016 4:54:32 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Coal Plant Decommissioning Program/Content/MultiColumn/Related Links

Related Links

12/3/2020 2:01:49 PM

sitecore\van.parker

12/3/2020 2:01:00 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Aero Reliability Project/Content/Main Content Container/Related Links

Related Links

5/21/2021 4:45:52 PM

sitecore\van.parker

5/21/2021 1:52:28 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Clermont/Content/Main Content Container/Related Links

Related Links

7/5/2017 4:41:30 PM

nam\rvp0696

7/5/2017 2:24:58 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Fairfax to Senco Substations/Content/Multi Column/Related Links

Related Links

7/15/2016 4:19:59 PM

NAM\SHaberm

5/5/2016 10:05:32 AM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Morgan Road/Content/MultiColumn/Related Links

Related Links

12/20/2017 2:11:41 PM

nam\rvp0696

12/19/2017 9:19:53 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Electric Transmission Projects/Warren to Nickel Project/Content/Main Copy MC/Related Links

Related Links

6/20/2018 3:18:02 PM

nam\rvp0696

6/20/2018 3:09:10 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Ethics/Content/Multi Column/Related Links

Related Links

7/15/2016 4:20:27 PM

NAM\SHaberm

3/29/2016 9:59:43 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:20:28 PM

NAM\SHaberm

3/29/2016 10:20:30 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Economic Benefits/Content/Multi Column/Related Links

Related Links

7/15/2016 4:20:29 PM

NAM\SHaberm

3/29/2016 9:59:43 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Environmental Impact/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:20:32 PM

NAM\SHaberm

3/29/2016 10:20:30 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/Nuclear Regulatory Process/Content/MultiColumn/Related Links

Related Links

9/9/2016 8:46:01 PM

nam\jholly1

9/8/2016 10:43:41 AM

NAM\LBorder

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Nuclear/FAQ/Content/Multi Column/Related Links

Related Links

9/9/2016 1:28:10 PM

default\Anonymous

9/1/2016 9:48:42 AM

nam\DMcGuir

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Natural Gas/FAQ/Content/Related Links

Related Links

9/27/2018 5:29:07 PM

nam\rvp0696

5/23/2018 9:05:08 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/New Generation/Natural Gas/W S Lee Station/Content/MC Main Content/Related Links

Safe Basin Closure Related Links

10/12/2016 3:46:41 PM

nam\rvp0696

10/12/2016 3:46:33 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/SafeBasinClosure/Safe Basin Closure Related Links

Idpd Experts Related Links

7/15/2016 4:16:24 PM

NAM\SHaberm

5/11/2016 9:52:16 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/IndependentExperts/Idpd Experts Related Links

Safe Recycling Related Links

10/26/2016 11:09:24 AM

NAM\RBGilbe

10/26/2016 10:46:26 AM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Content/PDP Our Plans/Safe Recycling/Safe Recycling Related Links

Safe Basin Closure Related Links

2/8/2017 8:22:55 AM

NAM\JWells4

10/12/2016 3:46:33 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Safe Basin Closure/Content/Safe Basin Closure Related Links

Safe Recycling Related Links

2/8/2017 8:21:54 AM

NAM\JWells4

10/26/2016 10:46:26 AM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Safe Recycling/Content/Safe Recycling Related Links

Groundwater Related Links

6/20/2018 10:05:04 AM

NAM\CBIcenh

6/20/2018 9:48:55 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Groundwater Studies/Content/Multi Column/Groundwater Related Links

Related Links new

11/6/2017 1:02:27 PM

nam\c81423

11/3/2017 9:33:56 AM

nam\c81423

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Groundwater Studies/2015 Groundwater Studies/Content/Multi Column New/Related Links new

Idpd Experts Related Links

2/8/2017 8:21:29 AM

NAM\JWells4

1/24/2017 12:10:08 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Independent Experts/Content/Idpd Experts Related Links

Related Links Media

11/16/2019 2:55:39 PM

sitecore\admin

2/3/2017 8:49:18 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Commitment Plant Communities/Content/Multi Column/Related Links Media

Related Links Research

11/16/2019 2:55:40 PM

sitecore\admin

2/3/2017 8:42:01 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Commitment Plant Communities/Content/Multi Column/Related Links Research

Related Links

10/20/2017 3:42:26 PM

nam\rvp0696

10/20/2017 3:20:26 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Ash Management/Water Plans/Content/Related Links

Related Links

7/15/2016 4:18:31 PM

NAM\SHaberm

3/30/2016 4:34:37 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Power Plants/Rogers Energy Complex/Content/Related Links

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DECNC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DECSC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEI/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEK/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEO/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEPNC/RL Smart Meter

RL Smart Meter

4/5/2018 10:46:30 AM

nam\rvp0696

8/22/2017 10:18:26 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Smart Meter/Content/MC Smart Meter DEPSC/RL Smart Meter

Related Links

12/14/2016 10:01:00 AM

NAM\JWells4

11/15/2016 2:45:59 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Grid Modernization FAQs/Content/Related Links

Related Links

7/14/2017 6:03:40 PM

nam\rvp0696

7/14/2017 5:57:02 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Coalition/Content/Multi Column/Related Links

Contact Us RL

7/15/2016 4:25:42 PM

NAM\SHaberm

4/25/2016 5:53:32 PM

nam\LPritch

/sitecore/content/JssPublic/Home/Our Company/About Us/Smart Grid/Coalition/Content/Multi Column/Contact Us RL

Related links

5/25/2018 9:43:20 AM

nam\rvp0696

5/17/2018 3:11:05 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Careers/Customer Service/Content/Related links

RL Video Links

8/31/2017 12:43:24 PM

nam\rvp0696

8/30/2017 4:56:25 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Cincinnati/Content/Multi Column/RL Video Links

Related Links

8/23/2016 2:51:43 PM

sitecore\Anonymous

3/30/2016 6:59:37 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Content/MultiColumn/Related Links

Related Links

7/15/2016 4:31:17 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Selective Catalytic Reduction/Content/Related Links

Related Links

7/15/2016 4:31:21 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Sulfur Dioxide Scrubbers/Content/Related Links

Related Links

7/15/2016 4:31:23 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Particulate Control Technologies/Content/Related Links

Copy of Related Links

7/15/2016 4:31:23 PM

NAM\SHaberm

3/29/2016 3:59:07 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Air Quality/Mercury Control Technologies/Content/Copy of Related Links

Related Links

7/15/2016 4:35:28 PM

NAM\SHaberm

3/31/2016 4:49:38 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Conservation Stewardship/Peregrine Falcons/Content/Related Links

Related Links

7/15/2016 4:35:41 PM

NAM\SHaberm

3/31/2016 4:54:56 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Our Company/Environment/Conservation Stewardship/Eagles/Content/Related Links

Related Links

4/26/2020 8:01:18 PM

sitecore\van.parker

6/11/2019 8:04:28 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Content/Intro Content Container/Related Links

Climate Reports

4/27/2020 9:13:34 PM

sitecore\van.parker

4/26/2020 7:59:35 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Content/Intro Content Container/Climate Reports

Related Links CDP

8/10/2016 1:39:13 PM

NAM\EYoung2

8/9/2016 1:14:08 PM

NAM\LBorder

/sitecore/content/JssPublic/Home/Our Company/Environment/Global Climate Change/Carbon Disclosure Project/Content/Related Links CDP

RL Additional Solar Projects DECDEPOHKY

1/20/2022 2:28:02 PM

sitecore\janet.kosoglov

1/20/2022 2:27:45 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL Additional Solar Projects DECDEPOHKY

Related Links ALL

7/12/2021 5:16:46 PM

sitecore\william.natta

7/12/2021 5:14:37 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links ALL

Related Links DEF

7/12/2021 5:17:15 PM

sitecore\william.natta

5/12/2017 1:49:51 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links DEF

Related Links DEI

7/12/2021 5:16:40 PM

sitecore\william.natta

5/12/2017 1:49:51 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/Related Links DEI

RL NC

1/20/2022 2:11:28 AM

sitecore\janet.kosoglov

7/12/2021 5:06:24 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL NC

RL SC

1/20/2022 2:11:43 AM

sitecore\janet.kosoglov

1/20/2022 2:11:43 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Content/Related Links/RL SC

Related Links DECSC

7/12/2021 5:37:10 PM

sitecore\william.natta

9/16/2016 3:11:18 PM

nam\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/SC Solar Energy Programs/Solar 101/Content/Related Links DECSC

Related Links DEPSC

7/12/2021 5:37:57 PM

sitecore\william.natta

9/16/2016 3:11:18 PM

nam\RBGilbe

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/SC Solar Energy Programs/Solar 101/Content/Related Links DEPSC

Related Links

11/16/2019 2:55:43 PM

sitecore\admin

9/9/2019 9:21:50 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Environment/Renewable Energy/Solar Energy/Solar Power in NC/Content/Related Links

Related Links

3/12/2018 4:44:39 PM

nam\rvp0696

3/12/2018 4:13:47 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Corporate Governance/Political Expenditures Policy/Content/Related Links

RL DukePAC Contributions

3/12/2018 4:05:01 PM

nam\rvp0696

8/28/2017 5:32:38 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Corporate Governance/Political Expenditures Policy/Content/RL DukePAC Contributions

Related Links Fin

8/19/2019 8:07:44 AM

nam\rvp0696

9/7/2016 1:41:51 PM

NAM\cball

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/Related Links Fin

Left col

8/28/2019 3:25:03 PM

nam\rvp0696

8/19/2019 8:10:24 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/SEC Filings by company/Left col

Right col

8/28/2019 3:25:00 PM

nam\rvp0696

8/19/2019 8:10:24 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Financials/Content/SEC Filings by company/Right col

Green Bond Projects

3/15/2022 1:45:59 AM

sitecore\van.parker

3/12/2022 5:24:42 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/DEF Sustainable Financing/Content/Section 1 Container/Green Bond Projects

Copy of Green Bond Projects

3/12/2022 5:55:16 PM

sitecore\van.parker

3/12/2022 5:24:42 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/DEF Sustainable Financing/Content/Section 2 Container/Copy of Green Bond Projects

Unapproved projects

1/9/2022 5:36:26 PM

sitecore\van.parker

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Unapproved projects

DEC Projects

9/24/2019 12:11:18 PM

nam\rvp0696

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Main Content Container/DEC Projects

DEP Projects

1/9/2022 5:37:12 PM

sitecore\van.parker

8/13/2019 3:18:17 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects/Content/Main Content Container/DEP Projects

DEC Projects

9/15/2020 5:19:22 PM

sitecore\van.parker

8/13/2019 3:05:53 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/Investors/Green Bonds/Solar Energy Projects DEF/Content/Main Content Container/DEC Projects

Related Links

11/16/2019 2:21:23 PM

sitecore\admin

7/1/2016 3:14:20 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Our Company/Investors/Individual Investors/Shareholder Services/Content/Related Links

Link to First Letter

7/20/2021 9:37:26 PM

sitecore\van.parker

5/19/2021 12:39:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Link to First Letter

Related Links Stakeholder

7/20/2021 1:37:06 PM

sitecore\van.parker

5/19/2021 12:39:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Related Links Stakeholder

Related Links

5/21/2021 3:33:03 PM

sitecore\janet.kosoglov

5/21/2021 3:32:59 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Our Company/Investors/Response to Elliott Management/Content/Multi Column New/Related Links

RL Video Links

10/27/2017 6:21:39 PM

nam\rvp0696

8/30/2017 4:56:25 PM

nam\rvp0696

/sitecore/content/JssPublic/Home/Our Company/KYFuture/Content/Multi Column/RL Video Links

Related Links Contacts

11/14/2019 10:57:24 AM

NAM\WNatta

10/11/2019 8:54:56 AM

NAM\WNatta

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Rates and Incentives/Content/Related Links Contacts

Related Links Sidebar

7/14/2017 9:25:24 AM

NAM\JWells4

3/31/2016 1:37:06 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Rates and Incentives/Content/Multi Column/Related Links Sidebar

Related Links Sidebar

3/16/2022 2:02:32 PM

sitecore\william.natta

3/31/2016 1:22:56 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Partner With Us/Economic Development/Indiana/Site Readiness Program/Content/Related Links Sidebar

Related Links

11/16/2019 2:22:07 PM

sitecore\admin

5/20/2016 1:34:20 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Retail Electric and Gas Sup/Gas/Firm Transportation/Content/Related Links

Contact Us

7/15/2016 4:59:12 PM

NAM\SHaberm

3/31/2016 9:17:44 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Suppliers/Supplier Diversity/Content/MC Header area/Contact Us

Related Links

7/15/2016 4:59:13 PM

NAM\SHaberm

3/31/2016 9:13:39 AM

nam\JWells4

/sitecore/content/JssPublic/Home/Partner With Us/Suppliers/Supplier Diversity/Content/MC Header area/Related Links

RL Brand Requirements

1/28/2022 2:46:54 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Brand Requirements/RL Brand Requirements

RL HVAC

1/28/2022 2:46:45 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCN HVAC Lighting/RL HVAC

RL Lighting

1/28/2022 2:46:31 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCN HVAC Lighting/RL Lighting

RL Smart Saver Worksheets

1/28/2022 2:46:09 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Smart Saver Worksheets/RL Smart Saver Worksheets

RL Marketing Collateral

1/28/2022 2:45:30 AM

sitecore\william.natta

12/8/2021 5:32:26 PM

sitecore\william.natta

/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Marketing Collateral/RL Marketing Collateral

Related Documents

2/6/2017 3:25:26 PM

NAM\JWells4

2/2/2017 11:46:49 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Nuclear Power/Content/Related Documents

Related Links

7/15/2016 5:21:28 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Energy From Coal/Content/Related Links

Related Links

7/15/2016 5:21:38 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Pumped Storage Hydro Plants/Content/Related Links

Related Links

7/15/2016 5:21:48 PM

NAM\SHaberm

4/5/2016 8:40:07 AM

nam\c81423

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Conventional Hydro Plants/Content/Related Links

Related Links

12/1/2016 8:52:32 AM

nam\jholly1

10/26/2016 11:30:40 AM

NAM\LAFrink

/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Delivering Electricity/Content/Related Links

Related Links NC SC IN KY

10/8/2020 8:55:03 PM

sitecore\janet.kosoglov

9/25/2020 8:42:50 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Energy Education/Energy Centers and Programs/Energy Revolution Energy Efficiency in Schools/energy efficiency kit/Content/Multi Column NC SC KY IN/Related Links NC SC IN KY

Related Links Right Sidebar

6/7/2018 10:34:34 AM

NAM\c81423

3/24/2016 10:18:53 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Content/Related Links Right Sidebar

External Related Links

7/15/2016 5:33:35 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/External Related Links

Internal Related Links

7/15/2016 5:33:35 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Internal Related Links

Related Links

11/16/2019 2:22:37 PM

sitecore\admin

3/24/2016 1:54:34 PM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Accordion Industry Collab/Related Links

Copy of External Related Links

7/15/2016 5:33:38 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Copy of External Related Links

External Related Links

7/15/2016 5:33:39 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/External Related Links

Internal Related Links

7/15/2016 5:33:47 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Internal Related Links

External Related Links

7/15/2016 5:33:53 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/External Related Links

Internal Related Links

7/15/2016 5:33:53 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/Internal Related Links

External Related Links

7/15/2016 5:33:55 PM

NAM\SHaberm

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/External Related Links

Internal Related Links

7/15/2016 5:33:55 PM

NAM\SHaberm

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Internal Related Links

Related Links

11/18/2016 1:06:17 PM

NAM\KMcCart

11/18/2016 1:05:23 PM

NAM\KMcCart

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Related Links

External Related Links

6/7/2018 10:03:41 AM

NAM\c81423

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/External Related Links

Internal Related Links

6/7/2018 10:03:45 AM

NAM\c81423

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/Internal Related Links

External Related Links

6/7/2018 11:37:58 AM

NAM\c81423

3/24/2016 2:54:16 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/External Related Links

Internal Related Links

6/7/2018 11:37:44 AM

NAM\c81423

4/7/2016 9:37:24 AM

NAM\tmitch3

/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/Internal Related Links

Related Links 1

2/10/2022 9:56:48 PM

sitecore\janet.kosoglov

2/8/2022 10:45:16 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Funding Guidelines/Content/Related Links 1

Related Links

4/3/2017 11:24:47 AM

NAM\RBGilbe

3/21/2017 2:30:44 PM

NAM\RBGilbe

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Community Initiatives/Charlotte Community Affairs/Content/Related Links

Copy of Related Links

11/16/2021 12:46:25 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Content/Multi Column New/Copy of Related Links

Related Links

3/29/2021 12:09:51 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown OLD/Content/Multi Column NoCache/Related Links

Related Links

2/16/2022 7:14:41 PM

sitecore\janet.kosoglov

2/16/2022 7:12:51 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown/Content/Multi Column/Related Links

Copy of Related Links

11/16/2021 12:48:08 AM

sitecore\janet.kosoglov

6/30/2020 8:38:46 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/South Carolina/Content/Multi Column New/Copy of Related Links

Related Links

3/3/2022 2:35:42 PM

sitecore\janet.kosoglov

2/25/2022 4:06:00 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/SC Storm Resiliency/Content/Related Links

Copy of Related Links

11/16/2021 12:44:06 AM

sitecore\janet.kosoglov

5/3/2019 12:26:29 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Florida/Content/Multi Column New/Copy of Related Links

Related Links

11/16/2021 12:42:40 AM

sitecore\janet.kosoglov

1/20/2020 6:12:18 PM

sitecore\Janet.Kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Ohio/Content/Multi Column New/Related Links

Copy of Related Links

11/17/2021 3:38:25 PM

sitecore\janet.kosoglov

11/17/2021 3:38:21 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Kentucky/Content/Multi Column New/Copy of Related Links

Copy of Related Links

11/17/2021 3:37:36 PM

sitecore\janet.kosoglov

11/17/2021 3:37:35 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Indiana/Content/Multi Column New/Copy of Related Links

Related Links

1/11/2019 1:44:34 PM

NAM\c81423

1/3/2019 9:38:31 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Online Application/Content/Related Links

RelatedLinks CountyList SHARED

10/11/2018 12:51:17 PM

NAM\c81423

10/5/2018 11:48:36 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/RelatedLinks CountyList SHARED

Related Links

5/22/2019 10:50:41 AM

NAM\c81423

5/3/2019 11:54:08 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/Multi Column New/Related Links

Related Links SHARED

5/22/2019 10:51:11 AM

NAM\c81423

5/3/2019 11:43:09 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Nature/Content/Multi Column New/Related Links SHARED

Related Links

5/22/2019 10:51:34 AM

NAM\c81423

5/3/2019 12:15:56 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Workforce Development/Content/Multi Column New/Related Links

Related Links

5/22/2019 10:52:06 AM

NAM\c81423

5/3/2019 12:16:40 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Local Impact/Content/Multi Column New/Related Links

Related Links Sidebar

1/11/2021 8:05:56 PM

sitecore\janet.kosoglov

6/2/2020 6:38:13 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Catawba/Content/Multi Column/Related Links Sidebar

Final Agreement

1/11/2021 8:05:56 PM

sitecore\janet.kosoglov

10/18/2018 9:39:45 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Catawba/Content/Multi Column/Final Agreement

Related Links 1

7/15/2016 5:59:16 PM

NAM\SHaberm

3/28/2016 3:05:42 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Content/Related Links 1

Related Links

7/15/2016 5:59:28 PM

NAM\SHaberm

3/28/2016 3:17:27 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Relicensing/Content/Related Links

Attachments

7/15/2016 5:59:28 PM

NAM\SHaberm

3/28/2016 3:26:44 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Hydroelectric Relicensing/Keowee Toxaway/Final License Application/Content/Attachments

Related Links 1

5/19/2020 5:10:43 PM

sitecore\janet.kosoglov

5/13/2020 2:02:15 AM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Content/Lake Services PDP/Catawba/Multi Column/Related Links 1

Related Links

1/11/2021 8:00:12 PM

sitecore\janet.kosoglov

10/24/2016 12:42:03 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Services/CW Shoreline Management Plan/Content/Long Copy MC/Related Links

Related Links

4/22/2020 3:42:42 PM

sitecore\janet.kosoglov

5/6/2016 2:10:40 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Services/CWHEP/Content/Multi Column/Related Links

Related Links Micro

1/14/2021 9:28:45 PM

sitecore\janet.kosoglov

12/15/2016 12:30:39 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KT Shoreline Management Plan/Content/Related Links Micro

Related Links micro

7/15/2016 6:01:47 PM

NAM\SHaberm

3/31/2016 4:01:27 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KT Shoreline Management Plan/Executive Summary/Content/Related Links micro

Related Links

6/3/2021 2:20:41 PM

sitecore\van.parker

5/6/2016 2:54:30 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Services/KTHEP/Content/Multi Column/Related Links

Related Links

7/15/2021 12:34:34 AM

sitecore\janet.kosoglov

7/13/2021 2:15:32 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Yadkin Pee Dee/Lake Tillery Neighbors/Content/Multi Column New/Related Links

Related Links

10/15/2020 6:06:03 PM

sitecore\janet.kosoglov

10/15/2020 6:05:58 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Nuisance Aquatic Plants/Content/MultiColumn/Related Links

Related Links

7/15/2016 6:02:26 PM

NAM\SHaberm

3/31/2016 4:40:21 PM

nam\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Services/Cultural Resources/Content/Related Links

Related Links

7/15/2016 6:03:16 PM

NAM\SHaberm

5/6/2016 4:14:51 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Recreation Information/2015 Hydro Rec Reports/Content/Related Links

Related Links

8/18/2021 8:08:51 PM

sitecore\janet.kosoglov

8/18/2021 7:05:47 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Lakes/Recreation Information/CW Recreation Enhancements/Content/Multi Column New/Related Links

Related Links

7/15/2016 6:03:35 PM

NAM\SHaberm

7/1/2016 12:41:49 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Lakes/Dam And Lake Level Basics/Content/Related Links

Related Links LIP Updates

7/16/2018 11:30:57 AM

NAM\c81423

7/16/2018 9:53:21 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Catawba Wateree DMAG/Content/Related Links LIP Updates

Related Links

3/31/2017 10:45:17 AM

NAM\JWells4

3/31/2017 9:23:41 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Keowee Toxaway DMAG/Content/Related Links

Related Links

2/7/2019 10:45:05 AM

NAM\c81423

3/31/2017 9:31:25 AM

NAM\JWells4

/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Yadkin Pee Dee DMAG/Content/Related Links

Related Links

2/17/2017 2:36:23 PM

nam\c81423

2/16/2017 1:45:48 PM

nam\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Content/Multi Column/Related Links

Related Links Micro

4/30/2021 5:47:47 PM

sitecore\jerry.wells

4/25/2016 9:30:17 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Distribution Lines Vegetation Management/Content/Distribution Lines MultiColumn/Related Links Micro

Related Links Micro

7/15/2016 6:05:48 PM

NAM\SHaberm

4/25/2016 9:42:13 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Transmission Lines Vegetation Management/Content/Related Links Micro

Related Links IN

11/16/2019 2:55:52 PM

sitecore\admin

4/1/2019 4:24:38 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links IN

Related Links KY

11/16/2019 2:55:52 PM

sitecore\admin

4/3/2019 10:10:47 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links KY

Related Links OH

11/16/2019 2:55:52 PM

sitecore\admin

4/3/2019 10:12:59 AM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links OH

Related Links Micro NC SC FL

11/16/2019 2:55:53 PM

sitecore\admin

4/1/2019 4:15:11 PM

NAM\c81423

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links Micro NC SC FL

Related Links Micro ALL

9/10/2021 7:04:16 PM

sitecore\janet.kosoglov

9/10/2021 7:04:12 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/How We Manage Rights of Way/Content/Related Links Micro ALL

Related Links Micro

7/15/2016 6:07:24 PM

NAM\SHaberm

4/25/2016 1:18:52 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Distribution Lines Guidelines and Restrictions/Content/Related Links Micro

Related Links Micro CAR

2/11/2022 6:54:40 PM

sitecore\janet.kosoglov

10/5/2021 1:56:42 AM

sitecore\van.parker

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro CAR

Related Links Micro FL

2/11/2022 6:55:10 PM

sitecore\janet.kosoglov

2/11/2022 6:55:09 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro FL

Related Links Micro MW

2/11/2022 6:55:35 PM

sitecore\janet.kosoglov

2/11/2022 6:55:34 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro MW

Related Links Micro

7/15/2016 6:07:40 PM

NAM\SHaberm

4/25/2016 1:14:59 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What is a Right of Way/Content/Related Links Micro

Related Links

2/8/2019 12:28:03 PM

nam\rvp0696

1/21/2019 9:46:03 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Emerald Ash Borer/Content/Intro Text Container/Related Links

NEW Related Links

5/14/2021 1:13:13 PM

sitecore\jerry.wells

9/6/2016 11:40:10 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/What is a Pipeline/Content/Multi Column/NEW Related Links

Related Links

5/14/2021 1:14:51 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Communicating with Prop Own/Content/Multi Column/Related Links

Related Links

5/14/2021 1:19:46 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Before you Plant or Build/Content/Multi Column/Related Links

Related Links

5/14/2021 1:22:12 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Identifying Pipelines/Content/Multi Column/Related Links

Related Links

5/14/2021 1:23:51 PM

sitecore\jerry.wells

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Construction/Content/Multi Column/Related Links

Related Links

9/12/2016 12:33:51 PM

nam\tmitch3

8/31/2016 10:32:06 AM

NAM\EYoung2

/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Clearance FAQ/Content/Related Links

Right Storm Related Links

11/16/2019 2:23:13 PM

sitecore\admin

10/7/2016 12:13:04 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Customer Service/Storms TESTING ONLY/Content/Right Storm Related Links

Right Storm Related Links

9/11/2017 7:08:44 PM

nam\rvp0696

10/7/2016 12:13:04 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Outages/Severe Weather/Content/Right Storm Related Links

Related Links right sidebar

11/3/2016 8:24:33 AM

NAM\CBIcenh

10/31/2016 9:55:49 AM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Safety and Preparedness/Contractors First Responders/Content/Multi Column/Related Links right sidebar

Related Links

10/2/2019 9:42:30 AM

nam\rvp0696

9/26/2019 8:46:25 AM

nam\rvp0696

/sitecore/content/JssPublic/Home/Safety and Preparedness/Overhead Power Lines/Content/Multi Column/Related Links

Related Links

7/15/2016 5:39:38 PM

NAM\SHaberm

5/5/2016 1:59:45 PM

nam\tmitch3

/sitecore/content/JssPublic/Home/Safety and Preparedness/Electric Safety/Content/Related Links

Related Links

12/21/2017 3:40:18 PM

NAM\CBIcenh

10/26/2017 12:54:07 PM

NAM\CBIcenh

/sitecore/content/JssPublic/Home/Spanish/Servicios al Hogar/StrikeStop/Content/Multi Column Strikestop/Related Links

Related Links

9/7/2016 7:40:59 PM

nam\tmitch3

9/6/2016 12:33:51 AM

nam\tmitch3

/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Pago Automatico/Content/Related Links

Related Links

11/16/2019 2:23:47 PM

sitecore\admin

6/6/2016 4:53:44 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Facturacion Presupuestada/Content/Related Links

Related Links

8/13/2020 8:51:08 PM

sitecore\van.parker

5/21/2020 8:34:53 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Payment Options/Multi Column/Related Links

Related Links all but FL

6/29/2021 4:06:36 PM

sitecore\jerry.wells

5/14/2020 8:45:05 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links all but FL

Related Links FL

6/29/2021 5:24:21 PM

sitecore\jerry.wells

5/14/2020 8:45:05 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links FL

Related Links DEC

6/16/2021 3:18:18 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEC

Related Links DEF

6/16/2021 4:05:43 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEF

Related Links DEI

6/16/2021 4:00:32 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEI

Related Links DEK

6/16/2021 3:58:08 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEK

Related Links DEO

6/16/2021 3:55:00 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEO

Related Links DEP

6/16/2021 3:49:44 AM

sitecore\william.natta

3/4/2020 6:24:39 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEP

Right col helpful resources

1/19/2022 11:21:42 PM

sitecore\van.parker

12/22/2021 7:08:09 PM

sitecore\van.parker

/sitecore/content/JssPublic/Home/info/ChoiceSupplierInfo/Content/Intro copy/Right col helpful resources

Related Links

9/16/2016 2:53:46 PM

NAM\JWells4

9/16/2016 2:53:41 PM

NAM\JWells4

/sitecore/content/JssPublic/Home/info/duke energy logo request/Content/Related Links

Copy of Related Links

11/9/2016 11:28:34 AM

nam\jholly1

11/9/2016 11:28:29 AM

nam\jholly1

/sitecore/content/JssPublic/Home/info/duke energy logo request/logo terms of use/Content/Copy of Related Links

Related Links

3/13/2019 11:18:15 AM

nam\spapai

3/13/2019 11:10:13 AM

nam\spapai

/sitecore/content/JssPublic/Home/Test/Automation/Components/MultiColumn/Content/Related Links

Related Links2

3/27/2019 9:06:01 AM

nam\kvelava

3/27/2019 8:46:30 AM

nam\kvelava

/sitecore/content/JssPublic/Home/Test/Automation/Components/MultiColumn/Content/Related Links2

Related Links

12/15/2021 7:57:55 PM

sitecore\janet.kosoglov

12/15/2021 7:57:50 PM

sitecore\janet.kosoglov

/sitecore/content/JssPublic/Home/Test/Validation/Content/Related Links

Related Links

9/7/2016 2:19:09 PM

NAM\SHaberm

9/7/2016 2:03:49 PM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/josh test page/Content/Related Links

Related Links

7/25/2016 9:35:21 AM

nam\jholly1

7/22/2016 9:20:05 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/Training Page/Content/Related Links

Related Links

7/15/2016 6:57:35 PM

NAM\SHaberm

4/28/2016 9:09:19 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Smoke Test/Smoke Test 2/Content/Related Links

Related Links

7/14/2017 8:25:29 AM

nam\jholly1

7/14/2017 7:52:59 AM

nam\jholly1

/sitecore/content/JssPublic/Home/Test/Regression/Multi Column/Content/Related Links

Related Links Left Sidebar

10/14/2021 3:28:16 PM

sitecore\matthew.evanoff

10/14/2021 2:59:32 PM

sitecore\matthew.evanoff

/sitecore/content/JssPublic/Home/JSSMigration/Multi Column/Content/Related Links Left Sidebar

Related Links Right Sidebar

10/14/2021 3:28:16 PM

sitecore\matthew.evanoff

10/14/2021 2:56:20 PM

sitecore\matthew.evanoff

/sitecore/content/JssPublic/Home/JSSMigration/Multi Column/Content/Related Links Right Sidebar

Related Links

10/27/2021 6:39:24 PM

sitecore\spencer.pope

10/27/2021 6:34:42 PM

sitecore\spencer.pope

/sitecore/content/JssPublic/Home/JSSMigration/Related Links/Content/Related Links

Related Links

9/28/2020 1:07:31 PM

sitecore\william.natta

9/2/2020 1:42:37 PM

sitecore\william.natta

/sitecore/content/ProjectSolutions/Home/Conflict Mitigation Processes Toolbox/Content/Related Links

Related Links

4/26/2021 3:37:42 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights NC/Content/MCNoCache/Related Links

Related Links

4/26/2021 3:37:19 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights SC/Content/MCNoCache/Related Links

Related Links

4/26/2021 3:37:00 PM

sitecore\william.natta

4/15/2021 3:58:04 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Our Company/About Piedmont/Our Community/Customer Bill of Rights TN/Content/MCNoCache/Related Links

RL News Releases

10/15/2020 11:58:05 PM

sitecore\jerry.wells

3/20/2020 3:09:09 AM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL News Releases

RL Resources

10/15/2020 11:58:05 PM

sitecore\jerry.wells

3/16/2020 12:43:07 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL Resources

RL State Regulatory Orders

10/15/2020 11:58:05 PM

sitecore\jerry.wells

6/2/2020 1:02:21 PM

sitecore\van.parker

/sitecore/content/PNG/Home/Customer Service/COVID 19/Content/RL State Regulatory Orders

RL News Releases

4/7/2020 1:02:46 PM

sitecore\william.natta

3/20/2020 3:09:09 AM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL News Releases

RL Resources

4/7/2020 1:01:33 PM

sitecore\william.natta

3/16/2020 12:43:07 PM

sitecore\william.natta

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL Resources

RL State Regulatory Orders

4/7/2020 1:01:00 PM

sitecore\william.natta

4/2/2020 5:15:23 PM

sitecore\courtney.icenhour

/sitecore/content/PNG/Home/Customer Service/COVID 19/SP/Content/RL State Regulatory Orders

export default function App() {
  return (
    <BrowserRouter>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/count">Count</Link>
            </li>
          </ul>
        </nav>
        <Switch>
          <Route path="/count">
            <Count />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}
function Count() {
  const [count, setCount] = useState(1);
  useEffect(() => {
    window.addEventListener('scroll', increaseCount);
    return () => window.removeEventListener('scroll', increaseCount);
  }, []);
  const increaseCount = () => {
    setCount(count => count + 1);
  }
  return <h2 style={{marginBottom: 1200}}>Count {count}</h2>;
}
function Count() {
  const [count, setCount] = useState(1);
  useEffect(() => {
    window.addEventListener('scroll', _.throttle(increaseCount, 100));
    return () => window.removeEventListener('scroll', _.throttle(increaseCount, 100));
  }, []);
  const increaseCount = () => {
    setCount(count => count + 1);
  }
  return <h2 style={{marginBottom: 1200}}>Count {count}</h2>;
}
const throttledCount = _.throttle(increaseCount, 100);
useEffect(() => {
    window.addEventListener('scroll', throttledCount);
    return () => window.removeEventListener('scroll', throttledCount);
  }, []);
useEffect(() => {
    const throttledCount = _.throttle(increaseCount, 100);
    window.addEventListener('scroll', throttledCount);
    return () => window.removeEventListener('scroll', throttledCount);
  }, []);
function Count() {
  const [count, setCount] = useState(1);
  const [text, setText] = useState("");
  const increaseCount = () => {
    setCount(count => count + 1);
  }
  const debouncedCount = _.debounce(increaseCount, 1000);
  const handleChange = (e) => {
    setText(e.target.value);
    debouncedCount();
  }
  return <>
    <h2>Count {count}</h2>
    <h3>Text {text}</h3>
    <input type="text" onChange={handleChange}></input>
  </>;
}
const debouncedCount = _.debounce(increaseCount, 1000);
const debouncedCount = useCallback(_.debounce(increaseCount, 1000),[]);
const debouncedCount = useRef(debounce(increaseCount, 1000)).current;
elements immediately follow each other.
  • Check that the description for each term is contained in one or more dd elements.

  • Check that the one or more dd elements immediately follow the one or more dt elements containing the term being described.

  • <dl title="Nautical terms">
      <dt>Knot</dt>
      <dd>
        <p>A <em>knot</em> is a unit of speed equaling 1
          nautical mile per hour (1.15 miles per hour or 1.852
          kilometers per hour).</p>
      </dd>
      <dt>Port</dt>
      <dd>
        <p><em>Port</em> is the nautical term (used on
          boats and ships) that refers to the left side
          of a ship, as perceived by a person facing towards
          the bow (the front of the vessel).</p>
      </dd>
      <dt>Starboard</dt>
      <dd>
        <p><em>Starboard</em> is the nautical term (used
          on boats and ships) that refers to the right
          side of a vessel, as perceived by a person
          facing towards the bow (the front of the vessel).</p>
      </dd>
    </dl>
    Success Criterion 3.1.3: Unusual Wordsarrow-up-right
    G55: Linking to definitionsarrow-up-right
    HTML5 Description listsarrow-up-right
    HTML 4 Definition lists: the DL, DT, and DD elementsarrow-up-right
    G62: Providing a glossaryarrow-up-right
    Let's talk about it.

    The purpose of this document is to create a discussion from which we may create a kind of Working Agreement around Code Review on Ninja Turtles. It sets out to do so by addressing the following:

    • The current state of our code review process

    • Suggestions about moving forward

    • How to improve at Code Reviews and PRs in general

    hashtag
    State of the Code Review

    The way that Ninja Turtles does Code Review is pretty much exclusively via Pull Requests; if you are a developer on Ninja Turtles, this is where most of your feedback will come from. At present, the most experienced of our team does most of the Code Review, and indeed it sometimes seems as though every PR is reviewed by 1 or 2 people, one of whom approves the PR before it is merged. Put simply, it's sort of the exemplification of the 80/20 rulearrow-up-right in action. For the purposes of this conversation, I will refer to the ones doing most of the reviewing as "the 20%" (following the adage that 80% of the work is done by 20% of the team) and the devs that are doing less of the reviewing as "the 80%". Obviously this is not exact, but you get the idea.

    The goal should be to get all developers to the point where they're contributing (and receiving) a more equitable share of Code Review, rather than the current 80/20 divide.

    hashtag
    Why Improve Code Review?

    As noted above, Ninja Turtles is a team that benefits from a diversity of experience levels, but the natural consequence of this is that the developers with the most knowledge and experience with code review (the 20%) end up doing most of the code review. This stands to reason, and is actually quite normal, and probably beneficial. But it can be taxing for those devs doing the lion's share of the reviewing. It is a knife that cuts both ways; while those experienced devs may begin to feel resentment that the responsibility rests on them, the other devs feel inadequate, that they're not pulling their weight, or that they aren't given a fair chance to review before the stuff in their wheelhouse is "taken". Thus, everyone gets further entrenched in their respective roles. This is not great, but it's a problem that won't solve itself. It will require action as a team (more on this later).

    But beyond that, there are several compelling reasons for us to improve on our Code Reviews.

    1. From a development standpoint, Code Review is the least expensive way to catch mistakes.

    2. Code Review provides opportunities to spread domain knowledge across the team, thereby preventing a complete reliance on trickle-down economics, er, knowledge.

    3. Code Reviews can provide opportunities to learn, mentor, and teach.

    4. Code Review builds a stronger foundation of trust or something

    "Effectiveness of code review for determining faults in software is between 30% and 35% more effective than standard unit testing"

    • Steve McConnell, Code Complete

    So, we understand the value of Code Review and we see a need to improve, but exactly how do we improve them?

    hashtag
    Going Forward

    I did a lot of reading and consulted some mentors to arrive at some of these conclusions. I will provide the resources I consulted below; I found them to be quite useful, and I encourage all who are interested to review them. After this, I propose a few changes. I will lay them out here and my hope is we can have a conversation about which changes we would like to adopt going forward and thus create something of a "Working Agreement" surrounding Code Reviews. Doing so obviously won't cause a miraculous change overnight, but I think implementing some of these suggestions is a good first step toward moving away from the 80/20 divide described above and getting everyone on the same level.

    hashtag
    General suggestions:

    • With two approvals now required to on a PR, perhaps it would make sense to rotate a more senior reviewer with a less senior reviewer or two (this would fall inline with guidelines I've seen) and let them tackle it independently. Ideally, they are both able to approve or provide feedback to the dev until they are ready to approve, but if not, after a certain period of time, the devs could come together and talk about why they are not feeling comfortable approving the PR.

    • Maybe we should define our style guidearrow-up-right a little bit more clearly (currently, it has exactly one entry ) and we should definitely set up linting to enforce some of the simple things, such as consistent formatting. Devs should not be pushing up code that has completely preventable errors and reviewers should not be wasting precious cognitive energy on simple errors that can be caught by computers

    • Consider limiting how much code you will look at, at least in one sitting; looking at code you didn't write for a long time can be exhausting. You might consider timeboxing your reviews to one hour and come back later if there's more you feel that needs to be done, or you may set a line count of around 200-400 lines before taking a break and doing something else for awhile.

    hashtag
    Things to think about and maybe discuss:

    • Expecting someone to get better at code reviews by simply looking at someone else's code just isn't a very good or efficient methodology. (See Jim Bird's article below). What can we do bridge that gap in a more proactive way? Perhaps some kind of structured "pair reviewing"?

    • I touched on this above but another thing that Jim Bird suggests is keeping the number of reviewers small. In the long run, I see the value in having all the devs swarm it and knocking out a PR, but in the shorter term, it might make sense to slow down and focus on the quality over quantity and getting all devs up to around the same contribution level?

    hashtag
    For the 20% currently doing 80% of the reviewing:

    • Consider alternating PRs. Maybe everyone doesn't need to look at every PR, but a few people look at different ones?

      • Not sure what this would look like, but I'm sure it's doable.

    • Step back. Don't be the first to review. Give some space to let some of the other devs (the 80%) step up. This will hopefully take a bit of the pressure off of you and lets the 80% leave the kind of feedback that may be easier for them to recognize as they level up their Code Review game.

      • This may mean that we add a Bitbucket integration to Slack or Teams (if that's available) or when a new PR is submitted, the submitter alerts the team

    • Be specific when answering questions. Answering a question about how or why something works with a block of code will solve the problem, but it doesn't help the asker understand the issue any better. A link to an article or MDN can be sufficient.

    hashtag
    For the 80%:

    • Have an algorithm/checklistarrow-up-right for things you want to review. Below you'll find an overview of the kinds of things to look for.

    • Step up.

    • If we implement the changes suggested above, you will have some room to make some suggestions early on. But having the 20% step back only works if the 80% step up.

    • Ask questions. Even in PRs. Questions like "Why are we doing it like this?" or "I'm having trouble understanding this block of code, can you explain it to me?" are totally valid within a PR. Doing it in the PR creates a paper trail of the conversation. If you've reviewed the code and you don't have feedback but you also don't feel you can approve the PR, it might be an indication that you need to ask more questions.

    • Ask to pair with somebody more experienced at reviewing code. You can get a feel for their process and ask questions about how they approach Code Reviews and spot things.

    • Don't underestimate the value of the input of someone with less context. Having anyone that isn't you review your code is helpful, because we all get myopic about our own code and can miss things that are really obvious to a fresh perspective.


    Sources:

    https://www.javacodegeeks.com/2014/08/dont-waste-time-on-code-reviews.htmlarrow-up-right

    https://www.youtube.com/watch?v=EXoSuqIp6Ns&feature=youtu.bearrow-up-right

    http://amlyhamm.com/talks/code-reviews/#/arrow-up-right

    https://www.braintreepayments.com/blog/effective-pull-requests-a-guide/arrow-up-right

    Code Reviewsarrow-up-right
    Oct 08, 2020arrow-up-right
    const Portal = ({ children }: PropsWithChildren<{}>) => {
    if (typeof window === 'undefined') return null;
    // Create the root element that content is rendered into
    // Used for "Portal" or other functionality that querySelectors for the '#root'
    let portalRoot = document.getElementById('root');
    if (!portalRoot) {
    portalRoot = document.createElement('div');
    portalRoot.setAttribute('id', 'root');
    portalRoot = document.body.appendChild(portalRoot);
    }
    return createPortal(children, portalRoot);
    };
    const Modal = ({ id, rendering }: ModalTypes) => {
    const { dispatch, state } = useBodyContext();
    const { activeId, isOpen } = state.modal;
    const { isEEActive } = useExperienceEditor();
    const shouldDisplayModal = activeId === id && isOpen;
    return isEEActive ? (
    <div className="border">
    The Modal Container
    <Placeholder name="jss-public-modal-container" rendering={rendering} />
    </div>
    ) : (
    <ModalComponent
    isActive={shouldDisplayModal}
    onCloseHandler={() => dispatch({ type: 'modalClose' })}
    {...{ id }}
    >
    <Placeholder name="jss-public-modal-container" rendering={rendering} />
    </ModalComponent>
    );
    };
    const ModalComponent = ({
    children,
    controls,
    isActive,
    onCloseHandler = () => {},
    }: React.PropsWithChildren<ModalComponentTypes>) => {
    const modalBackgroundRef = useRef<HTMLDivElement>(null);
    const modalContainerRef = useRef<HTMLDivElement>(null);
    const isMobile = !useMediaQuery('md');
    // when the modal is active, do not allow the body to scroll
    usePreventBodyScroll(isActive);
    // classes for the modal 'background' div
    const activeClass = isActive ? 'bg-black bg-opacity-80 pointer-events-auto z-modal' : '';
    // classes for the modal 'container' div
    const containerBackgroundClass = controls?.isTransparent ? '' : 'border border-gray bg-white';
    const containerScrollClass = controls?.preventScroll ? '' : 'overflow-auto';
    const containerClass = `${containerBackgroundClass} ${containerScrollClass}`;
    // closes the modal when clicking outside of the modal
    const handleClose = (event: React.MouseEvent<HTMLDivElement>) => {
    if (
    event &&
    event.target &&
    event.target instanceof Element &&
    event.target.contains(modalBackgroundRef.current) &&
    isActive
    ) {
    onCloseHandler();
    }
    };
    useFocusTrap({
    shouldTrap: isActive,
    container: modalBackgroundRef.current,
    onExit: () => onCloseHandler(),
    onEnter: ([first]) => first?.focus(),
    });
    return (
    <Portal>
    <div
    className={`js-modal fixed h-screen w-full top-0 left-0 flex flex-col items-center p-12 pointer-events-none ${activeClass}`}
    ref={modalBackgroundRef}
    {...a11yAction(handleClose)}
    role="dialog"
    aria-hidden={!isActive}
    tabIndex={-1}
    >
    <Transition.RevealDown
    active={isActive}
    className={`relative px-24 pt-32 pb-24 m-auto w-full max-w-3xl ${containerClass}`}
    >
    <div ref={modalContainerRef}>
    <button
    aria-label="close"
    className="visible absolute right-0 top-0 mr-24 md:mt-16 mt-0 z-overlay"
    onClick={onCloseHandler}
    >
    <SvgLoader
    aria-hidden={true}
    focusable={false}
    color={controls?.isTransparent ? 'text-white' : 'text-teal-dark'}
    name="X"
    size={isMobile ? 20 : 24}
    />
    </button>
    {children}
    </div>
    </Transition.RevealDown>
    </div>
    </Portal>
    );
    };
    export { Modal as default, ModalComponent };

    Typescript Tricks

    Component Creation

    README

    Analytics

    Adding analytics to a component is pretty straightforward,

    First set up your analytics object in your composition method:

    const { compositionFunction, component } = Composition(NavCard)(({ fields, params }) => {
    
    const items = fields?.items.reduce(
        (acc: Parameters<typeof NavCard>[0]['items'], curr: typeof fields) => {
          return [
            {
              ...
              analytics: {
                category: 'nav-card_rectangular',
                label: title?.value || '',
                action: [cta?.text?.value, cta?.href].filter(Boolean).join(' | '),
                guid: link?.value?.id || '',
                event: 'event-click',
              },
            },
          ];
        },
        []
      );

    Then import the track object from src/lib/Analytics into your component, and grab the analytics data from the composition props. After that, simply attach a handler to the correct component node, to call the track.component method and send the data to analytics:

    hashtag
    Testing Analytics

    When not in production, analytics data will be sent to the console instead of to Google. To verify that your analytics are being setup and used correctly, simply open the console, trigger the analytics method, and you should see something similar to the following:

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    import track from 'src/lib/Analytics';
    ...
    
    const ComponentName = ({
      ...
      analytics,
    }: PropsType) => {
    
    ...
      <Button
        ...
        onClick={() => track.component(analytics)}
      >
        Click Me!
      </Button>

    Ensure that images clearly communicate the concept across all breakpoints and that critical information is not cropped out.

    START NOWarrow-up-right
    Web Accessibility: Linksarrow-up-right
    ,
    {
    useState
    ,
    useRef
    ,
    useEffect
    }
    from
    "
    react
    "
    ;
    import { SecondaryNavType } from "./types";
    import { NavItem } from "./components/NavItem";
    import { DropDownMenu } from "./components/DropDownMenu";
    import { useLocation } from "react-router-dom";
    const SecondaryNav = ({ items, suppressNav }: SecondaryNavType) => {
    const currentItems = items[0];
    const [active, setActive] = useState(-1);
    let { pathname } = useLocation();
    pathname = pathname?.split(" ").join("-").toLowerCase();
    const isItemActive = (index: number) => active === index;
    // Need to check if the current page is the route of a top level page or any subpages or subpages' subpages so we can display the glorious teal bar
    const selectedIndex: Array<number> = [];
    let topLevelSelected: number;
    if (currentItems) {
    // check if the current page is one of the top level navItems in the blue bar
    topLevelSelected = currentItems.subpages.findIndex(
    (x) => x.route.toLowerCase() === pathname
    );
    // check if the current page is in any of the subpages
    for (let i = 0; i < currentItems.subpages?.length; i++) {
    const select = currentItems.subpages[i]?.subpages?.findIndex(
    (x) => x.route.toLowerCase() === pathname
    );
    if (select !== -1) {
    selectedIndex.push(i);
    break;
    } else {
    for (
    let ii = 0;
    ii < currentItems.subpages[i]?.subpages[ii]?.subpages?.length;
    ii++
    ) {
    const select = currentItems.subpages[i]?.subpages[
    ii
    ]?.subpages?.findIndex((x) => x.route.toLowerCase() === pathname);
    if (select !== -1) {
    selectedIndex.push(i);
    break;
    }
    }
    }
    }
    }
    const isItemSelected = (index: number) => selectedIndex.indexOf(index) !== -1;
    const hasSubPages = currentItems?.subpages.length > 0;
    const getNumberOfCols = (index: number) => {
    if (currentItems?.subpages[index].subpages[0]?.subpages?.length) {
    return currentItems.subpages[index].subpages.length;
    }
    return 1;
    };
    const dropdownDirection = (index: number) => {
    const navItemsLength = currentItems?.subpages.length;
    const numberOfCols = getNumberOfCols(index);
    const placeInNav = index + 1;
    const displayToRight = navItemsLength - placeInNav >= numberOfCols;
    const displayToLeft = placeInNav > numberOfCols;
    const leftSide = placeInNav <= navItemsLength / 2;
    if (displayToRight) {
    return { side: "left-0", position: "relative", cols: numberOfCols };
    } else if (displayToLeft) {
    return { side: "right-0", position: "relative", cols: numberOfCols };
    } else if (leftSide) {
    return { side: "left-0", position: "", cols: numberOfCols };
    } else {
    return { side: "right-0", position: "", cols: numberOfCols };
    }
    };
    const rootElement = useRef<HTMLDivElement | null>(null);
    const activeButton = useRef<HTMLButtonElement>(null);
    useEffect(() => {
    const handleClick = (event: Event) => {
    const cTarget = event.target as Element;
    if (
    (cTarget instanceof HTMLElement &&
    !rootElement.current?.contains(cTarget)) ||
    cTarget.tagName === "A" ||
    cTarget.tagName === "SPAN"
    ) {
    setActive(-1);
    }
    };
    window.addEventListener("click", handleClick);
    return () => {
    window.removeEventListener("click", handleClick);
    };
    }, []);
    useEffect(() => {
    const handlePress = (event: KeyboardEvent) => {
    const { key } = event;
    if (key === "Escape") {
    activeButton?.current?.focus();
    setActive(-1);
    }
    };
    window.addEventListener("keyup", handlePress);
    return () => {
    window.removeEventListener("keyup", handlePress);
    };
    }, []);
    // Close menu when scrolled off screen
    useEffect(() => {
    const handleScroll = ({ currentTarget }: Event) => {
    if (!rootElement.current) {
    return;
    }
    const headerDimensions = rootElement.current.getBoundingClientRect();
    const headerDistanceFromTop = headerDimensions.top;
    const headerHeight = headerDimensions.height;
    const scrollingDown = (currentTarget as Window).scrollY > 0;
    const headerOutOfView = headerDistanceFromTop + headerHeight < 0;
    // offScreen
    const offScreen = headerOutOfView && scrollingDown;
    if (offScreen) {
    // Close menu
    setActive(-1);
    }
    };
    // TODO Throttle event listeners
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => {
    window.removeEventListener("scroll", handleScroll);
    };
    }, []);
    if (suppressNav || !hasSubPages) {
    return null;
    }
    return (
    <div
    className="hidden xl:block xl:-mt-px px-16 md:px-24 bg-blue text-white"
    ref={rootElement}
    >
    <nav className="relative container-4xl h-full" aria-label="Secondary">
    <ul className="flex justify-between -mx-16 h-full">
    {currentItems.subpages.map(({ subpages, ...rest }, index) => {
    const isActive = isItemActive(index);
    const isSelected =
    isItemSelected(index) || topLevelSelected === index;
    return (
    <li className={dropdownDirection(index).position} key={index}>
    <NavItem
    {...rest}
    currentRef={isActive ? activeButton : null}
    isActive={isActive}
    isSelected={isSelected}
    onClick={() => setActive(isActive ? -1 : index)}
    type={subpages.length ? "button" : "link"}
    />
    <DropDownMenu
    {...{
    subpages,
    ...{
    ...rest,
    isActive,
    dropdownDirection: dropdownDirection(index),
    },
    }}
    />
    </li>
    );
    })}
    </ul>
    </nav>
    </div>
    );
    };
    export default SecondaryNav;
  • Projectsarrow-up-right

  • Issuesarrow-up-right

  • Boardsarrow-up-right

  • Time in Statusarrow-up-right

  • Createarrow-up-right

  • Give feedback to Atlassianarrow-up-right

  • Helparrow-up-right

  • Administrationarrow-up-right

  • arrow-up-right

  • Welcome to Duke Energy Enterprise JIRA ~ Please enter a ticket through MyIT if you have any significant problems with the tool. Thank you! Have a great day!

    arrow-up-right

    DXT Ninja Turtlesarrow-up-right

    ======================================================================================================

    DNT boardarrow-up-right

    • User story maparrow-up-right

    • Kanban boardarrow-up-right

    • Releasesarrow-up-right

    1. DXT Ninja Turtlesarrow-up-right

    2. DNT-2654 a11y audit - Formarrow-up-right

    3. DNT-2659arrow-up-right

    Details

    • Type: Sub-task

    • Status:REVIEW (View Workflowarrow-up-right)

    • Priority: Medium

    • Resolution:Unresolved

    • Labels:

      None

    Description

    (MultiStepForm issue) (Stepper component)

    Notes:

    The check mark that indicates a completed step in the form is an <svg> image without a text alternative. Blind and low vision users will not know that this is a completed step.

    Recommendation

    Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element.

    SVG with <title> element, role="img", and the aria-labelledby attribute (Simplified code)

    Complete ...

    Contextually Marking up accessible images and SVGsarrow-up-right

    • Options

    Attachments

    Drop files to attach, or browse.

    1. arrow-up-right

      36546.pngarrow-up-right

      5 kB

      Mar/08/22 10:54 AM

    2. arrow-up-right

      187 kB

      1 hour ago

    • Add Linkarrow-up-right

    Issue Links

    is blocked by

    DNT-2794arrow-up-right Add title support to SvgLoader

    • DONE

    Delete this linkarrow-up-right

    relates to

    DNT-2661arrow-up-right Lists must be contained within semantically correct containers

    • REVIEW

    Delete this linkarrow-up-right

    Activity

    • Allarrow-up-right

    • Comments

    • Work Logarrow-up-right

    Permalinkarrow-up-right Editarrow-up-right Deletearrow-up-right

    Guner, Bryanarrow-up-right added a comment - 1 hour ago

    Validated in test environment:

    • Commentarrow-up-right

    People

    Assignee:

    Guner, Bryan

    Reporter:

    Sowa, Bill

    Votes:

    0 arrow-up-rightVote for this issuearrow-up-right

    Watchers:

    2 arrow-up-rightStop watching this issuearrow-up-right

    Dates

    Created:

    Feb/10/22 8:42 AM

    Updated:

    18 minutes ago

    Development

    • 1 brancharrow-up-right

      Updated 6 days ago

    • 5 commitsarrow-up-right

      Latest 5 days ago

    • MERGED

      Updated 7 hours ago

    Agile

    Future Sprint:

    Sprint 14arrow-up-right

    View on Boardarrow-up-right

    Hipchat discussions

    Do you want to discuss this issue? Connect to Hipchat.

    ConnectDismissarrow-up-right

    [3/1 2:28 PM] Macias, Marcie

    home-services/gas-line-repair/enroll

    [3/1 2:42 PM] Macias, Marcie

    home/start-stop-move/stop

    Linked Applicationsarrow-up-right
    arrow-up-right
    Dashboardsarrow-up-right
    Sign Up for Site Manager Training »

    Sign Up for Site Manager Training »

    LAN IDs, Employee IDs, and Responsibility Codes do not currently show in modern search on a person's profile card. Please use our specialized People Search Page or access the information in Delve.

    The Portal is your default home page when you launch the Internet on your Duke Energy workstation. You can also access the Portal from your personal laptop or mobile device.

    The Portal is a robust communication tool designed to be informative and engaging. On the home page, top company stories and employee features are posted frequently. The home page also contains media articles, company events, and personnel announcements. You are encouraged to add comments to articles and submit company-sponsored events and accomplishments.

    The Portal also provides access to key transactional items such as company facts and branding, HR information (e.g., benefits and pay), policies, services (e.g., mail service and expense management), departments, operations, and other tools and applications.

    hashtag
    Portal Site Managers

    Brand, Creative & Digital

    Contact & Personal Information

    Contingent Worker Actions

    COVID-19 Employee Resources

    COVID-19 Manager Resources

    Customer Experience & Services

    hashtag
    PORTAL GUIDE

    hashtag
    Manage Your Information

    Delve profile pages exist for everyone with a LAN ID who is an employee of or contingent worker for Duke Energy.

    Where did my profile information come from?

    The organizational and location information in your Delve Portal profile comes from Active Directory.

    This section of your Delve Profile is optional. You can choose to enter additional information about yourself, including:

    • About me

    • Home phone

    • Projects

    • Skills and expertise

    • Schools and education

    • Interests and hobbies

    Filling in this information will help others find you when they're looking for someone with knowledge or experience about your areas of expertise, or it can help you connect with others who share your interests.

    To edit this information, go to your Delve profilearrow-up-right and click the Update Profile button.

    Manage Your Photo in Workday

    Manage Your Photo in O365

    Instructions for O365 Photos

    When you go to Delve, click Me in the left navigation. Use the camera icon over your photo to change your picture. This will change it throughout the O365 environment, including Outlook, Teams, and SharePoint. Changing your photo here will not push it to Workday. Work is underway to enable the Workforce Photo Tool to integrate with O365.

    Photo Guidelines

    Your photo must be business-appropriate. That would be:

    • A forward-facing, head-and-shoulders shot of you without sunglasses, ball cap, kids or family dog serves as a good starting point. Consider the limited space that will display your face when you select a photo. The goal is to see what you look like!

    • Please follow copyright rules when selecting a photo to update. You must own rights to any image you post.

    hashtag
    PORTAL GUIDE

    hashtag
    Accessing the Portal Remotely

    You can access the Portal from any computer or mobile device. You do not need to be on the Duke Energy network, and you do not need to use a Duke Energy computer or device that is in the Personal Mobile Device program.

    From the SharePoint App

    • You can download the SharePoint app from an app store. The home icon in the app takes you to the Portal homepage.

    From a Mobile Browser

    • Open your web browser (i.e., Edge, Chrome or a similar Web browser)

    • Type https://dukeenergy.sharepoint.com/sites/portalarrow-up-right in the address bar at the top of your Web browser and press Enter.

    • If you are not on the network or on a device managed through the Personal Mobile Device program, you will need to log into your Office 365 account.

    • Enter your LAN ID and password.

    • Follow the steps required for your multi-factored authentication (MFA). This is most likely a phone call to the number you entered when you created your Office 365 profile. For questions, .

    Keep in mind that some Duke Energy applications, are not available when you are off the network, and you will experience errors if you click links to them.

    hashtag
    PORTAL GUIDE

    hashtag
    Intranet Design Awards

    The top ten intranets in the world, published annually by the Nielsen Norman Group. We purchase and publish these reports so that we can get a glimpse behind firewalls into the best practices with other companies. Duke Energy is a two-time winner.

    hashtag
    PORTAL GUIDE

    The Modern Portal Project Is Complete! »
    Site Manager Guide»
    Welcome to Support Chat. Please enter a question and a chat technician will soon be with you. Just type in a question, keyword or phrase below and I’ll take you to the information you’re looking for.

    Bryan1:59 PM

    Hi, I have a macbook air... is it at all possible for me to find a workaround that allows for me to run two external monitors in addition to the builtin one?

    System1:59 PM

    The following associated data has been added:

    • Customer Information

    • Incident: INC000032293420

    System1:59 PM

    The following associated data has been modified:

    • Customer Information

    • Incident: INC000032293420

    System1:59 PM

    System Message: Jamal Clair is online and ready to chat.

    Jamal Clair1:59 PM

    Hello my name is Jamal. Can you please provide your employee id, contact number, Computer serial number, and current location please?

    System2:02 PM

    System Message: undefined has rejoined the session

    2:03 PM

    Contact number: 551-254-5505... Serial Number is: FVFGX02PQ6L8 and my current location is NY,NY. I am not sure what my employee number is or how to access it but I am going to try to look into it.

    2:04 PM

    Could it be this: CW-Professional 47146?

    Jamal Clair2:04 PM

    Thank you

    Jamal Clair2:04 PM

    Do you have a dockingstation

    Jamal Clair2:04 PM

    docking station

    2:08 PM

    Yes two actually, but unfortunately the macbook air was the m1 chip was the one model apple made that doesn'‌t nativley support multiple screens. This is one of my first weeks, I think my manager said it was an accident and that I was supposed to be sent a macbook pro but I have no idea if that is still in the works. Various sources on the internet claim to have found workarounds but so far I have not been able to successfully replicate these '‌hacks'‌. If the firewall allowed me to cast via airplay then I think I could connect to my external displays wirelessly. As I speak this computer is driving three large external screens but all three are a mirror of each other with the only distinction being the builtin display which is not mirroring the other three screens.

    Jamal Clair2:09 PM

    is this your personal device

    2:09 PM

    no it is my duke device

    2:10 PM

    My personal computer is a pc which has no issue with this

    Jamal Clair2:10 PM

    so you want to be able to control multiple screens with airplay

    2:10 PM

    As a last resort

    Jamal Clair2:10 PM

    but duke firewall blocks that

    2:11 PM

    ideally I would be able to drive two or three external screens in the (extend... (as opposed to mirror))-display mode

    2:13 PM

    I don'‌t have much expertice but I figured someone else may have had this issue and found some other solution... like for instance an external gpu that can drive the extra screens ? idk tbh but prior to this job I got very comfortable with a three screen workflow and it isn'‌t the end of the world but going back down to two or one screens is a pretty significant inconvenience considering one is usually dedicated to a microsoft teams call / screen share

    2:15 PM

    I feel like I may be blowing this out of proportion... this is just a matter of preference that I have grown accustomed to and is not actually a necessity. I have a creeping fear that it may just not be possible with this computer.

    \

    hashtag
    BitLocker keys for LAPTOP-9LGJ3JGS

    • OPERATING SYSTEM DRIVEKey ID:\ f035f7c1-b011-4a97-ad86-a1e2b994037d\ Recovery key:\ 277827-520718-692857-678557-601997-238381-127116-121748

    https://dukeenergy-vchat.onbmc.com/eschat/chat.jsp#/chatarrow-up-right
    Jokes aside the items in this list are concepts that I usually see beginners struggling with. At the same time, learning these concepts will vastyly improve your testing game. I know it did with mine.

    1. Everything is a DOM node

    This is usually the first misconception that beginners have when they start approaching Testing Library. It is especially true for those developers like me that came from Enzymearrow-up-right.

    Many think that getByText and other helper methods return some special wrapper around the React component. People even ask how to implement a getByReactComponent helper.

    When you work with Testing Library you are dealing with DOM nodesarrow-up-right. This is made clear by the first Guiding Principlearrow-up-right:

    If it relates to rendering components, then it should deal with DOM nodes rather than component instances, and it should not encourage dealing with component instances.

    If you want to check for yourself, it's as simple as this:

    Once you realize that you're dealing with DOM nodes you can start taking advantage of all the DOM APIs like querySelectorarrow-up-right or closestarrow-up-right👍

    2. debug's optional parameter

    Since we now know that we're dealing with a DOM structure, it would be helpful to be able to "see" it. This is what debugarrow-up-right is meant for:

    ometimes thought debug's output can be very long and difficult to navigate. In those cases, you might want to isolate a subtree of your whole structure. You can do this easily by passing a node to debug:

    3. Restrict your queries with within

    Imagine you're testing a component that renders this structure:

    You want to test that each ID gets its correct value. You can't use getByText('Apples') because there are two nodes with that value. Even if that wasn't the case you have no guarantee that the text is in the correct row.

    What you want to do is to run getByText only inside the row you're considering at the moment. This is exactly what withinarrow-up-right is for:

    4. Queries accept functions too

    You have probably seen an error like this one:

    Usually, it happens because your HTML looks like this:

    The solution is contained inside the error message: "[...] you can provide a function for your text matcher [...]".

    What's that all about? It turns out matchers accept strings, regular expressions or functions.

    The function gets called for each node you're rendering. It receives two arguments: the node's content and the node itself. All you have to do is to return true or false depending on if the node is the one you want.

    An example will clarify it:

    We're ignoring the content argument because in this case, it will either be "Hello", "world" or an empty string.

    What we are checking instead is that the current node has the right textContentarrow-up-right. hasText is a little helper function to do that. I declared it to keep things clean.

    That's not all though. Our div is not the only node with the text we're looking for. For example, body in this case has the same text. To avoid returning more nodes than needed we are making sure that none of the children has the same text as its parent. In this way we're making sure that the node we're returning is the smallest—in other words the one closes to the bottom of our DOM tree.

    5. You can simulate browsers events with user-event

    Ok, this one is a shameless plug since I'm the author of user-event. Still, people—myself included—find it useful. Maybe you will too.

    All user-event tries to do is to simulate the events a real user would do while interacting with your application. What does it mean? Imagine you have an input field, and in your tests, you want to enter some text in it. You would probably do something like this:

    It works but it doesn't simulate what happens in the browser. A real user would most likely move the mouse to select the input field and then start typing one character at the time. This, in turns, fires many events (blur, focus, mouseEnter, keyDown, keyUp...). user-event simulates all those events for you:

    Unable to find an element with the text: Hello world.
    This could be because the text is broken up by multiple elements.
    In this case, you can provide a function for your text
    matcher to make your matcher more flexible.
    <div>Hello <span>world</span></div>
    import React from "react";
    import
    
    import React from "react";
    import
    
    const { debug } = render(<MyComponent
    
    const { debug } = render(<MyComponent
    
    <table>
      <thead>
        <tr
    
    import React from "react";
    import
    
    import { render, screen, within } 
    
    fireEvent.change(input, { target: { 
    import userEvent from "@testing-library/user-event";
    
    

    0

    4

    typesc

    1

    381

    20

    361

    test

    1

    262

    14

    248

    Gitbook Action Build

    DefinitionOfDone

    hashtag
    Component Creation - Definition of Done


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    Our "Definition of Done" for a Component

    You will know your component is complete if it is properly formatted, you have the requisite files outlined in the previous section (where applicable), and your files are free of TypeScript and ESLint warnings. Additionally, the appearance of the component should match the look of the Abstract design if one is provided, and if any Acceptance Criteria is provided, that should be met as well. We prioritize accessibility as well, so your component should pass [WCAG2.1 a11y requirements](https: //www.w3.org/WAI/standards-guidelines/wcag/glance/). This can be validated via the Storybook plugin we have set up, as well as in the browser using Lighthouse.

    Lastly, you will need to have written some unit tests to check the core functionality of your component and ensure that those are passing.

    hashtag
    Checklist


    In summary, your component should do the following...


    If you have met all of these requirements, then you, intrepid developer, have a component worthy of submission to PR!

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Sitecore

    hashtag
    Component Creation - Sitecore


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    Sitecore and You

    In the context of our application, we treat Sitecore like a CMS for our React front end. We use it to tell our application where components should be and provide data and assets.

    But Sitecore is not for the faint of heart. Many devs like to get their hands dirty by just "getting in there" and poking around. However, due to its terrible UI and grinding sluggishness, working within Sitecore can be maddening. To top it all off, as is so often the case, the documentation for Sitecore is substandard. God willing, you won't need to venture into Sitecore very often. But if you have exhausted all other options and you still find you need to work within it, it's better to go in with a clear gameplan of what you want to accomplish and how to accomplish it. This section aims to help you with that.

    hashtag
    Sitecore Component Creation Overview

    Once upon a time, new components were built by the front end team and then handed off to the Sitecore developers for integration into the application. These days, components built by the front end team stay within the front end application, making the workflow much more efficient. New components can then be integrated into the CMS for use by the content team, with editable fields intact.

    While many components are already available for our application in Sitecore, if you find that you need to add a component to be edited within Sitecore, this document contains an overview to the process you will need to follow.

    Before delving into that, it is helpful to understand at a high level how Sitecore is being used in our implementation.

    A "page" such as /Home or /Billing consists of a collection of Renderings within Sitecore. A rendering could be viewed as a representation of a React component within Sitecore. These renderings are associated with data relevant to the component and placeholder info. A Placeholder is a way for content authors and marketing folks to specify where upon a page a component should be injected. In order for a rendering to be displayed upon a page, it will need to be assigned a placeholder.

    When a page exists within Sitecore, it is made available to our application via something called the Layout Service, which exposes an endpoint that can be requested. This endpoint returns a serialized JSON payload of the rendering info from Sitecore. While a traditional Sitecore implementation would involve C# and Razor templates, this implementation makes it possible for us to use React on the front end.

    hashtag
    Basic Instructions for Creating a Component

    1. Navigate to [Sitecore](http: //scdev25.duke-energy.com/sitecore) and login. From there, expand "sitecore > Layout > Renderings > Custom > Common" and you can see the available renderings. If you need a rendering to map to your component that isn't already available here, you can create one.

    2. Create a Rendering. Within the appropriate folder under /sitecore/layout/renderings, create a new Json Rendering. This will most likely be within the Custom/Common folder, and can be done either by right-clicking the folder and selecting Insert > Json Rendering or selecting the folder you want to create a rendering in and then pressing the Json Rendering button from the options.

    < Previous

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Installing Citrix Workspace on Chrome OS Devices

    Installing, Configuring, and Uninstalling Citrix Workspace on Chrome OS Devices

    Contents

    ****

    Installing Citrix Workspace on Chrome OS Devices ............................................................. 2

    Configuring Citrix Workspace on Chrome OS Devices.......................................................... 3

    Secondary configuration of Citrix Workspace on Chrome OS Devices…………………. 6

    Uninstalling Citrix Workspace on Chrome OS Devices ......................................................... 9

    \

    Installing Citrix Workspace on Chrome OS Devices

    1. On the Chrome OS device, open the Chrome Web Store and search for ‘Citrix Workspace’. Select ‘Citrix Workspace for Chrome OS’. Click Add to Chrome and Add App When prompted.

    ****

    Configuring Citrix Workspace on Chrome OS Devices

    ****

    1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.

    ****

    2. You will be prompted to enter a server address: Enter

    ****

    3. Next, enter your Username, Password and your Passcode when prompted. Click Log On once you’ve finished entering your information.

    NOTE:

    • When using a physical key fob, your Passcode is your unique PIN + RSA token ID.

    • When using a soft token, the PIN is not included as part of the Passcode.

    4. The available Citrix applications and desktops should now appear as icons. You have successfully installed the Citrix Workspace software on your computer.

    5. To launch a virtual desktop, select the Desktops tab at the bottom of the screen and select the assigned desktop. And to launch a virtual application, select the Apps tab at the bottom of the screen and select the application.

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    ****

    Secondary configuration of Citrix Workspace on Chrome OS Devices

    ****

    ****

    NOTE: If you have not set up an authentication method in O365, by using this job aid , please do so BEFORE adding the Multifactor Authentication Citrix Portal URL

    ****

      • Be sure you set up a primary authentication method and a secondary authentication method.

    ****

    o Choose your preferred primary authentication method from the drop-down box under “what’s your preferred option?”

    ****

    1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.

    2. You will be prompted to enter a server address: Enter in order to setup the Citrix Portal that utilizes Multifactor Authentication. Afterwards, select Connect.

    3. Next, enter your Username and Password when prompted. Click Log On once you’ve finished entering your information.

    ****

    NOTE: You will be prompted to authenticate via the method that you setup within your Office 365 Authentication Profile:

    · Via a call to a phone number you provided during setup (e.g., mobile phone number, secondary mobile phone number, or home phone number where you will be working)

    · The Microsoft Authenticator application

    ****

    ****

    4. To access your assigned virtual desktops or virtual applications, click either the DESKTOPS or APPS buttons at the top of the page.

    ****

    Uninstalling Citrix Workspace on Chrome OS Devices

    1. Open the Launcher (Magnifying glass) on your taskbar. Click the upward arrow to show all installed apps and locate the Citrix Workspace icon.

    2. Right-click the Citrix Workspace Icon. If you are not using a mouse, then press the Alt key when you click on the Citrix Workspace icon to bring up the ‘right click’ menu options

    3. Click Uninstall. Click Remove when asked to confirm removal.

    ****

    Setup

    This section outlines getting the application (for the JSS public site) setup for development.

    1. Get the repo

      1. cd to where you keep your repos

    TechnicalOverview

    hashtag
    Component Creation - Technical Overview


    Table of Contents:

    1. Intro

    👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Intro

    hashtag
    Component Creation - Intro


    Table of Contents:

    1. Intro

    🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverview

    hashtag
    Component Creation - Practical Overview


    Table of Contents:

    1. Intro

    Forms

    Dynamically generating forms with React and Sitecore JSS

    hashtag
    Creating the FormField

    1. Navigate to page http: //local.duke-energy.com:3000/home/products/outdoor-lighting/contact

    <!--
      Hero Section wrapper
      - Component below hero is required to use a white background color
    -->
    
    <section class="relative 2xl:px-24 2xl:pt-24">
    
      <!--
        Hero Container
      -->
    
      <div class="relative container-5xl flex flex-col justify-center aspect-16/12 sm:aspect-16/9 md:aspect-16/7 lg:aspect-16/6 xl:aspect-16/5 px-24 sm:px-32 md:px-48 py-64 md:py-48">
    
        <!--
          - Leave alt attribute empty
          - Use 'srcset' and 'sizes' attributes to optimize the hero image
        -->
    
        <img
          class="absolute top-0 left-0 object-cover object-right w-full h-full 2xl:rounded-lg bg-gray-light"
          src="image.jpg?w=800&as=1&bc=ffffff"
          srcset="image.jpg?w=800&as=1&bc=ffffff 800w,
                  image.jpg?w=1600&as=1&bc=ffffff 1600w"
          sizes="(min-width: 768px) 1600px, 800px"
          alt=""
          width="1600"
          height="500"
          loading="lazy"
        >
    
        <!--
          Overlay
          - Change background gradient colors based on theme of hero
          - Default - "from-blue-dark to-blue sm:via-blue"
        -->
    
        <div
          class="absolute top-0 left-0 h-full w-full 2xl:rounded-l-lg md:w-3/4 bg-gradient-to-tr md:bg-gradient-to-r md:to-transparent opacity-80"
          aria-hidden="true"
        ></div>
    
        <!--
          Content Container
        -->
    
        <div class="relative w-full container-xs md:container-4xl">
    
          <!--
            If dark theme/gradient, text is white
              - Use "text-white"
            If light theme/gradient, text can be gray or blue
              - Use "text-gray-dark" -or- "text-blue"
            Default
              - "text-white"
          -->
    
          <div class="w-full md:w-1/2 text-center md:text-left">
    
            <!-- Hero Title
              - If this is the page title, you should use an <h1> element
              - Use an <h2> element if you already have a page title on the page
            -->
            <h2
              class="text-3xl md:text-2xl-fixed xl:text-3xl"
              id="hero-title"
            >
              Hero Title
            </h2>
    
            <!-- Hero Short Description-->
            <p class="text-lg xl:text-xl mt-12 lg:mt-16">
              Description goes here.
            </p>
    
            <!-- Hero Actions -->
            <div class="flex justify-center md:justify-start gap-16 lg:gap-24 mt-16 lg:mt-24">
              <!--
                If dark theme/gradient
                  - Use "btn-primary-reversed"
                If light theme/gradient
                  - Use "btn-primary"
                Default
                  - "btn-primary-reversed"
              -->
              <a
                class="btn btn-primary-reversed btn-md"
                href="#"
                id="hero-primary-action"
                aria-labelledby="hero-primary-action hero-title"
              >
                Primary Action
              </a>
              <!--
                If dark theme/gradient
                  - Use "btn-secondary-reversed"
                If light theme/gradient
                  - Use "btn-secondary"
                Default
                  - "btn-secondary-reversed"
              -->
              <a
                class="btn btn-secondary-reversed btn-sm lg:btn-md"
                href="#"
                id="hero-secondary-action"
                aria-labelledby="hero-secondary-action hero-title"
              >
                Secondary Action
              </a>
            </div>
          </div>
    
        </div>
    
      </div>
    
    </section>
    // # TextMatch type accenpts function: https://testing-library.com/docs/queries/about/#textmatch
    import { renderWithCTX, screen, Matcher } from "src/lib/testWrappers";
    import userEvent from "@testing-library/user-event";
    import "@testing-library/jest-dom";
    import { Accordion, themeMap } from "./index";
    import { compositionFunction } from "./composition";
    import data from "./data";
    import { stripHTMLTags } from "src/lib/helpers";
    jest.mock("src/lib/useIntersection");
    describe("Accordion", () => {
      const props = compositionFunction(data);
      it.skip("should render the correct items", () => {
        renderWithCTX(<Accordion {...props} />);
        for (let i = 0; i < props.items.length - 1; i++) {
          const accordionTitle = screen.getByText(
            props?.items[0]?.title?.value as string
          );
          expect(accordionTitle).toBeTruthy();
        }
      });
      it.skip("should render Accordion Component with image", () => {
        renderWithCTX(<Accordion {...props} />);
        const img = screen.getByRole("img", { name: /facebook/i });
        expect(img).toBeInTheDocument();
      });
      it("should reveal text when user clicks", async () => {
        renderWithCTX(<Accordion {...props} />);
        const accordionButton = screen.getByRole("button", {
          name: props.items[0].title?.value,
        });
        // # Full Manual Query:
        // (This is actually not good - getByText won't respect eg aria- attrs like getByRole so we aren't actually testing anything)
        // These are [not exposed to a11y tree](https://testing-library.com/docs/queries/about/#priority)
        // 👉
        // screen.getByText(
        //   /proin laoreet mauris vel urna tempor ultricies\. duis rhoncus lorem sed tellus egestas bibendum\. in aliquam mauris est, vel condimentum metus aliquet a\. nunc volutpat tincidunt nisl luctus pretium\. sagittis elit non, vestibulum metus\. mauris maximus vitae magna in mattis\. integer interdum maximus felis sed placerat\. nam lobortis tellus non felis fermentum, vitae venenatis ligula congue\. donec sit amet luctus odio\. magna commodo, sodales convallis ante scelerisque\. etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc\. curabitur in orci libero\. morbi tincidunt ante vel sem rutrum tempor\. cras ac purus quis urna maximus volutpat\./i
        // );
        // # We can still do a findByText,
        // but we need to check ourselves whether it is accessible:
        // 👉
        const hiddenText = await screen.findByText((content, element) => {
          const hasTextContent =
            element?.innerHTML === props?.items[0]?.text?.value;
          return hasTextContent;
        });
        expect(hiddenText).toBeInTheDocument();
        expect(hiddenText).toHaveAttribute("aria-hidden", "true");
        await userEvent.click(accordionButton);
        // # Compare against textContent stripHTMLTags:
        // (similar to https://polvara.me/posts/five-things-you-didnt-know-about-testing-library)
        // 👉
        // const noTag = stripHTMLTags(props?.items?.[0]?.text?.value as string);
        // const [revealedText] = await screen.findAllByText(
        //   // @ts-ignore
        //   // eslint-disable-next-line
        //   (content, element) => element?.textContent === noTag
        //   // ?  console.log('🔥', content) || true : false
        // );
        // expect(revealedText).toBeInTheDocument();
        // console.log('😳', noTag);
        // 😳 Proin laoreet mauris vel urna tempor ultricies. Duis rhoncus lorem sed tellus egestas bibendum. In aliquam mauris est, vel condimentum metus aliquet a. Nunc volutpat tincidunt nisl luctus pretium. Duis et ligula semper, sagittis elit non, vestibulum metus. Mauris maximus vitae magna in mattis. Integer interdum maximus felis sed placerat. Nam lobortis tellus non felis fermentum, vitae venenatis ligula congue. Donec sit amet luctus odio. Nam convallis justo vitae magna commodo, sodales convallis ante scelerisque. Etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc. Curabitur in orci libero. Morbi tincidunt ante vel sem rutrum tempor. Cras ac purus quis urna maximus volutpat.
        // # Find by innerHTML:
        // 👉
        const revealedText = await screen.findByText((content, element) => {
          const hasTextContent =
            element?.innerHTML === props?.items[0]?.text?.value;
          return hasTextContent;
        });
        expect(revealedText).toBeInTheDocument();
        expect(revealedText).toHaveAttribute("aria-hidden", "false");
      });
      // Overall, probably ultimately more durable / less error prone to use a testid and check aria- attrs
      it.skip("renders the correct default style", () => {
        const { rerender } = renderWithCTX(<Accordion {...props} />);
        const button = screen.getByRole("button", {
          name: /accordion trigger one/i,
        });
        expect(button).toHaveClass(themeMap.default.button);
        const altThemeProp = { ...props, theme: "footer" } as const;
        rerender(<Accordion {...altThemeProp} />);
        expect(button).toHaveClass(themeMap?.footer?.button);
      });
    });
    address any provided Acceptance Criteria
  • You will be prompted by a modal to choose a name for your item. In the input labelled "Enter a name for the new item", provide the name of the React component that will be used for the rendering.

  • You can now add your newly-created Json Rendering to a page. If appropriate, you will also need to specify a datasource item for the rendering. The datasource item will need to be created from a data template in Sitecore.

  • When Layout Service is rendering, the fields from the datasource item will be serialized and added to the Layout Service output and should now be available to our application.

  • Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • hashtag
    Introduction

    To understand how a component is displayed within our application, it helps to understand the process by which it is rendered within our React app.

    This is a somewhat complex process, but the simplified overview is this:

    • When a user navigates to a route, the React app receives a big bundle of data from Sitecore, including a list of components, and the relevant data for those components.

    • That list represents the building blocks of the page that the user has navigated to.

    • Those "building blocks" are checked against the component list of our application, and when a match is found, a React component is returned, along with the data that Sitecore sent along with the component that it needs to render properly.

    • If a match is not found, a "placeholder component" is rendered instead. Within the React app, this is exactly what it sounds like: a big, ugly "placeholder" to remind you that this component has not yet been created for the app:

    Oops! 🙀 we dont have Global Alerts yet!\

    Let's dive in and get a little more insight.

    Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Types VS Interfaces

    hashtag
    Types vs. interfaces in TypeScript - LogRocket Blog

    Excerpt

    It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?


    The idea of having static type-checking in JavaScript is really fantastic and the adoption of TypeScript is growing more every day.

    You started to use TypeScript in your project, you created your first type, then you jumped to your first interface, and you got it working. You concluded that TypeScript, in fact, was helping your development and saving you precious time, but you might have made some mistakes and not followed the best practices when you started to work with types and interfaces in TypeScript.

    This is the case for a lot of developers, they don't really know the real difference between type aliases and interfaces in TypeScript.

    It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?

    hashtag
    Types and type aliases

    Before we jump into the differences between types and interfaces in TypeScript, we need to understand something.

    In TypeScript, we have a lot of basic types, such as string, boolean, and number. These are the basic types of TypeScript. You can check the list of all the basic types [here](https: //www.typescriptlang.org/docs/handbook/basic-types.html#table-of-contents). Also, in TypeScript, we have advanced types and in these [advanced types](https: //www.typescriptlang.org/docs/handbook/advanced-types.html), we have something called [type aliases](https: //www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases). With type aliases, we can create a new name for a type but we don't define a new type.

    We use the type keyword to create a new type alias, that's why some people might get confused and think that it's creating a new type when they're only creating a new name for a type. So, when you hear someone talking about the differences between types and interfaces, like in this article, you can assume that this person is talking about type aliases vs interfaces.

    We will use the [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) for code examples. The [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) allows us to work and test the latest version of TypeScript (or any other version we want to), and we will save time by using this playground rather than creating a new TypeScript project just for examples.

    hashtag
    Types vs. interfaces

    The difference between types and interfaces in TypeScript used to be more clear, but with the latest versions of TypeScript, they're becoming more similar.

    Interfaces are basically a way to describe data shapes, for example, an object.

    Type is a definition of a type of data, for example, a union, primitive, intersection, tuple, or any other type.

    hashtag
    Declaration merging

    One thing that's possible to do with interfaces but are not with types is declaration merging. Declaration merging happens when the TypeScript compiler merges two or more interfaces that share the same name into only one declaration.

    Let's imagine that we have two interfaces called Song, with different properties:

    interface Song { artistName: string; }; interface Song { songName: string; }; const song: Song = { artistName: "Freddie", songName: "The Chain" };

    TypeScript will automatically merge both interfaces declarations into one, so when we use this Song interface, we'll have both properties.

    Declaration merging does not work with types. If we try to create two types with the same names, but with different properties, TypeScript would still throw us an error.

    Duplicate identifier Song.

    hashtag
    Extends and implements

    In TypeScript, we can easily extend and implement interfaces. This is not possible with types though.

    Interfaces in TypeScript can extend classes, this is a very awesome concept that helps a lot in a more object-oriented way of programming. We can also create classes implementing interfaces.

    For example, let's imagine that we have a class called Car and an interface called NewCar, we can easily extend this class using an interface:

    class Car { printCar = () => { console.log("this is my car") } }; interface NewCar extends Car { name: string; }; class NewestCar implements NewCar { name: "Car"; constructor(engine:string) { this.name = name } printCar = () => { console.log("this is my car") } };

    hashtag
    Intersection

    Intersection allows us to combine multiple types into a single one type. To create an intersection type, we have to use the & keyword:

    type Name = { name: "string" }; type Age = { age: number }; type Person = Name & Age;

    The nice thing here is that we can create a new intersection type combining two interfaces, for example, but not the other way around. We cannot create an interface combining two types, because it doesn't work:

    interface Name { name: "string" }; interface Age { age: number }; type Person = Name & Age;

    hashtag
    Unions

    Union types allow us to create a new type that can have a value of one or a few more types. To create a union type, we have to use the | keyword.

    type Man = { name: "string" }; type Woman = { name: "string" }; type Person = Man | Woman;

    Similar to intersections, we can create a new union type combining two interfaces, for example, but not the other way around:

    interface Man { name: "string" }; interface Woman { name: "string" }; type Person = Man | Woman;

    hashtag
    Tuples

    [Tuples](https: //www.typescriptlang.org/docs/handbook/basic-types.html#tuple) are a very helpful concept in TypeScript, it brought to us this new data type that includes two sets of values of different data types.

    type Reponse = [string, number]

    But, in TypeScript, we can only declare tuples using types and not interfaces. There's no way we can declare a tuple in TypeScript using an interface, but you still are able to use a tuple inside an interface, like this:

    interface Response { value: [string, number] }

    We can see that we can achieve the same result as using types with interfaces. So, here comes the question that a lot of developers might have — should I use a type instead of an interface? If so, when should I use a type?

    Let's understand the best use cases for both of them, so you don't need to abandon one for the other.

    hashtag
    What should I use?

    This question is really tricky, and the answer to it, you might guess, depends on what you're building and what you're working on.

    Interfaces are better when you need to define a new object or method of an object. For example, in React applications, when you need to define the props that a specific component is going to receive, it's ideal to use interface over types:

    interface TodoProps { name: string; isCompleted: boolean }; const Todo: React.FC<TodoProps> = ({ name, isCompleted }) => { ... };

    Types are better when you need to create functions, for example. Let's imagine that we have a function that's going to return an object called, type alias is more recommended for this approach:

    type Person = { name: string, age: number }; type ReturnPerson = ( person: Person ) => Person; const returnPerson: ReturnPerson = (person) => { return person; };

    At the end of the day, to decide if you should use a type alias or an interface, you should carefully think and analyze the situation — what you're working on, the specific code, etc.

    Interface work better with objects and method objects, and types are better to work with functions, complex types, etc.

    You should not start to use one and delete the other. Instead of doing that, start to refactor slowly, thinking of what makes more sense to that specific situation.

    Remember that you can use both together and they will work fine. The idea here is just to clarify the differences between types and interfaces, and the best use cases for both.

    hashtag
    Conclusion

    In this article, we learned more about the differences between types and interfaces in TypeScript. We learned that type aliases are advanced types in TypeScript, we learned the best use cases for both types and interfaces in TypeScript, and how we can apply both of them in real projects.

    hashtag

    Warnings

    Typescript Rules:

    Special Props Warning

    Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.

    For instance, attempting to access this.props.key from a component (i.e., the render function or propTypes) is not defined. If you need to access the same value within the child component, you should pass it as a different prop (ex: <ListItemWrapper key={result.id} id={result.id} />). While this may seem redundant, it's important to separate app logic from reconciling hints.

    clone the project from https: //bitbucketp.duke-energy.com/projects/DUKCOM/repos/dxt-jss-public/browse 1. In your terminal (command line), run git clone https: //bitbucketp.duke-energy.com/scm/dukcom/dxt-jss-public.git
  • Install dependencies

    1. Use node -v 14.17.4

    2. Set npm registry to Nexus (prod)* 1. run npm config set registry https: //nexus.duke-energy.com/repository/duke-cne-npm/ on the command line

    3. Run npm install on the command line

  • Setup .jssconfig and .env

    1. Add scjssconfig.json file (or rename scjssconfig-sample.json → scjssconfig.json)

      //scjsstest.duke-energy.com" } }

  • Add .env file (or rename .env-sample → .env)

    1. Add:

      //` for development so that jurisdiction cookies will work properly.

  • Setup your .hosts file

    1. We need to set the local IP to local.duke-energy.com so we can use cookies set by the .duke-energy.com domain.

    2. On the command line, run sudo vim /etc/hosts

    3. Add a new line and enter 127.0.0.1 local.duke-energy.com

    4. Press Esc key and then :x to save and exit

  • Start the project for development.

    1. Inside the project, run npm run start:connected on the command line.

    2. After the app successfully starts up, change the URL to https: //local.duke-energy.com:3000/home.

  • Do some cool stuff

  • *Why are we using Nexus rather than npm for package installs?

    @de-electron is a scoped npm package for [Electron Design System](https: //electron.duke-energy.com) dependencies which lives in Nexus, Duke Energy's private internal package repository. You can use Nexus to install all necessary npm packages for the project.

    Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • If you'd prefer to get right to the point and skip the overview, there's a handy checklist herearrow-up-right. Otherwise, read on for an in-depth explanation of how one could approach a workflow via Storybook.

    hashtag
    Workflow - A Practical Overview

    As a developer, you may find it convenient to develop your React component in isolation within Storybook, rather than developing on the page. This can give you the opportunity to preview it and get a feel for the data you will need. Once the component looks and behaves in the intended manner, it should be relatively simple to incorporate it into the broader application.

    Where before we have focused on a logical overview of how components work and are used within the context of our Sitecore JSS setup and our custom React application, this section will focus on a practical suggested workflow for developing your component.

    hashtag
    The Approach

    Whether or not you elect to decide to develop your React component with Storybook, you will need two things to get started: an index.tsx and the data to render with it (if your component gets data from Sitecore).

    It will be challenging to know in advance the shape of the data that comes to your React component from Sitecore, so you may find it helpful to set up your component in a very basic way that simply accepts props and renders or logs them out. Then, you can set up a composition function that does the same.

    Once your component is rendering, you can capture the data from Sitecore that is logged or displayed onscreen to create your data file. Then you can begin working on your composition function to ensure you are sending only the data your component needs in the simplest, flattest form possible.

    As you consider the data your component will need, it might be a good time to think about how your component is supposed to behave and consider writing out some assertions to test against in your test.ts file.

    After that, you can work in Storybook by importing your component's composition function into your story.js file and process your dummy data with it. At this point, you will have dummy data that looks just like what is coming from Sitecore and you can develop your component in Storybook, just as you would a regular website.

    An example of that might look something like this:

    Finally, once your component is close to completion, you can finish writing your unit tests to ensure your component functions as expected.

    The following section summarizes things into a concise checklist.

    hashtag
    Checklist

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

      1. Add:
    
         ```
         {
           "sitecore": {
             "instancePath": "",
             "apiKey": "{9F777224-3275-4D56-BD29-371FB3C00821}",
             "deploySecret": "",
             "deployUrl": "",
             "layoutServiceHost": "https:
      ```
      HTTPS=true
      ```
    
      This will allow you to use `https:
    // MyComponent/composition.tsx
    
    const MyComponent = (sitecoreData) => {
      return sitecoreData;
    };
    // MyComponent/index.tsx
    
    /** technically, you would want to provide types in a Typescript file,
     * but we're just trying to get some data here.
     */
    
    const MyComponent = (scData) => {
      console.log(scData);
      return <div>{JSON.stringify(scData)}</div>;
    };
    
    // Now you can copy the payload from your browser and paste it into your brand new data file!
    // story.js
    import React from "react";
    import MyComponent from "./index";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    import Data from "./data";
    
    /**
     * create a variable with data that is in the shape that your component will be
     * expecting, as it would be from your composition file.
     * Again, this is necessary since the composition function is not called by Storybook.
     */
    
    const props = MyComponentComposition(Data);
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      /* here you can spread those props into your Storybook file, making data
       * available to your component as it would be on the actual live page.
       */
      ...props,
    };

    Your office phone may automatically pre-fill; HOWEVER, be sure to uncheck this option. Your primary and secondary methods should be a combination of: a mobile phone number, secondary mobile phone number, a home phone number where you will be working or the Authenticator applicationarrow-up-right

  • The Authenticator application is the preferred primary method of authentication.

  • https://www.myapps-duke-energy.comarrow-up-right
    How to Add or Change your O365 Profile Authentication methodarrow-up-right
    https://www2.myapps-duke-energy.com/Citrix/Duke-Energy-MFAWeb/arrow-up-right
    {
    render
    ,
    screen
    }
    from
    "
    @testing-library/react
    "
    ;
    test("everything is a node", () => {
    const Foo = () => <div>Hello</div>;
    render(<Foo />);
    expect(screen.getByText("Hello")).toBeInstanceOf(Node);
    });
    {
    render
    ,
    screen
    }
    from
    "
    @testing-library/react
    "
    ;
    test("the button has type of reset", () => {
    const ResetButton = () => (
    <button type="reset">
    <div>Reset</div>
    </button>
    );
    render(<ResetButton />);
    const node = screen.getByText("Reset");
    // This won't work because `node` is the `<div>`
    // expect(node).toHaveProperty("type", "reset");
    expect(node.closest("button")).toHaveProperty("type", "reset");
    });
    />
    )
    ;
    debug();
    />
    )
    ;
    const button = screen.getByText("Click me").closest();
    debug(button);
    >
    <th>ID</th>
    <th>Fruit</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>1</td>
    <td>Apples</td>
    </tr>
    <tr>
    <td>2</td>
    <td>Oranges</td>
    </tr>
    <tr>
    <td>3</td>
    <td>Apples</td>
    </tr>
    </tbody>
    </tabl
    {
    render
    ,
    screen
    ,
    within
    }
    from
    "
    @testing-library/react
    "
    ;
    //
    highlight-line
    import "jest-dom/extend-expect";
    test("the values are in the table", () => {
    const MyTable = ({ values }) => (
    <table>
    <thead>
    <tr>
    <th>ID</th>
    <th>Fruits</th>
    </tr>
    </thead>
    <tbody>
    {values.map(([id, fruit]) => (
    <tr key={id}>
    <td>{id}</td>
    <td>{fruit}</td>
    </tr>
    ))}
    </tbody>
    </table>
    );
    const values = [
    ["1", "Apples"],
    ["2", "Oranges"],
    ["3", "Apples"],
    ];
    render(<MyTable values={values} />);
    values.forEach(([id, fruit]) => {
    const row = screen.getByText(id).closest("tr");
    // highlight-start
    const utils = within(row);
    expect(utils.getByText(id)).toBeInTheDocument();
    expect(utils.getByText(fruit)).toBeInTheDocument();
    // highlight-end
    });
    });
    j;
    from
    "
    @testing-library/react
    "
    ;
    import "jest-dom/extend-expect";
    test("pass functions to matchers", () => {
    const Hello = () => (
    <div>
    Hello <span>world</span>
    </div>
    );
    render(<Hello />);
    // These won't match
    // getByText("Hello world");
    // getByText(/Hello world/);
    screen.getByText((content, node) => {
    const hasText = (node) => node.textContent === "Hello world";
    const nodeHasText = hasText(node);
    const childrenDontHaveText = Array.from(node.children).every(
    (child) => !hasText(child)
    );
    return nodeHasText && childrenDontHaveText;
    });
    });
    value
    :
    "
    Hello world
    "
    }
    }
    )
    ;
    userEvent.type(input, "Hello world");

    Technical Overview

  • Practical Overview

  • Definition of Done

  • Sitecore


  • hashtag
    A Technical Overview


    hashtag
    Generate Component Factory

    The Generate Component Factory (scripts/generate-component-factory.js) generates the /src/temp/componentFactory.js file which maps React components to JSS components.

    The component factory is a mapping between a string name and a React component instance. When the Sitecore Layout service returns a layout definition, it returns named components. This mapping is used to construct the component hierarchy for the layout.

    The default convention uses the parent folder name as the component name, but it is customizable in generateComponentFactory().

    Sometimes Sitecore references a component in a way that is different from what we want the component to be called within the application. In this case, we can provide an alias to map the Sitecore name to our component upon import:

    In this case, we are telling our app that if it receives a string called HeroCarousel or HeroCarouselNocache, use the 'Hero' component. Likewise, if a string of JssAccordion is encountered, use the 'Accordion' component, and so on.

    If the name of your component and its Sitecore counterpart are the same, there is no need to include an alias.

    hashtag
    Passing Down Data Via Composition

    At its simplest, the goal of the composition file is to transform the data being passed to your component from accessing deeply nested properties like this:

    into this:

    What does that entail exactly?

    The composition file also exists in the your component directory. Sitecore sends a wealth of information down to the page related to each component. Not all of this information is needed or even useful. So the purpose of each of these functions is to strip away the unneeded data, giving our component only what it needs to render properly.

    To illustrate this, in the following example, the QuickLinks component will get a single items prop returned to it, which will consist of an array of objects:

    As a developer, you will be responsible for writing the composition function for your component. Since the data from Sitecore comes in all sizes and shapes, there is no "one-size-fits-all" solution, but most likely you can take a cue from the composition functions that have been written before to get a feel for how to approach a new one.

    hashtag
    Adding files for your component

    Now that you know how components are implemented within our app, how is a component actually created within the app?

    Generally, each component you create will consist of at least one of the following items:

    • an index.tsx filearrow-up-right - your React file.

    • a composition.tsxarrow-up-right file for transforming incoming data into more concise props

    • a test.tsx filearrow-up-right - a file for your unit tests.

    • a - for Typescript definitions and interfaces

    • a - for Storybook..

    • a - for mock Sitecore data.

    • a - It's a folder.

    Read on for more detail.

    hashtag
    Index.tsx

    The index file will be your main component React file, using Typescript. At a minimum, your component will require this file. Since our project uses Tailwind, your styles will mainly exist here as well. See the styles sectionarrow-up-right for more info.

    hashtag
    Test.tsx

    The test file is where your unit tests live. Tests are written in React with Typescript, using [Jest](https: //jestjs.io/en/) and [Enzyme](https: //enzymejs.github.io/enzyme/).

    Jest is a popular testing library and Enzyme adds some sugar on top to make writing the tests a little easier.

    You should be testing the basic functionality of your component. This may be as simple as validating that your component is rendering to mocking data and checking to make sure your component is outputting it correctly.

    In all likelihood, you will need a test for every component you write, even if it is a very small test to confirm that the component is properly mounted.

    hashtag
    Types.ts

    This file is for type definitions and interfaces for [Typescript](https: //www.typescriptlang.org). You can store your component-specific definitions in this file and export them to your index file.

    For more information on writing Typescript within our app, please check out our Typescript Style Guide.

    hashtag
    Stories.js

    The stories.js file creates a Storybookarrow-up-right component. To be honest, the paradigm of writing in Storybook can be a little bit tricky to grasp at first if you don't have experience with it. And to be frank, their docs could use some work. So you are encouraged to check out our documentation for Storybook.arrow-up-right.

    In essence, however, consider your Storybook file something like an environment for your React component that enables you to run it in isolation from the rest of the app. This makes it easy to preview the functionality and, as a result, developers and designers can easily access the React component without having to navigate to a route to interact with it. This also enables you to view and manipulate it in ways that would be difficult in the context of an entire app.

    If your component is extremely simple and self-contained, a Storybook file may not be necessary, or perhaps even possible. But in general, all components should have a Storybook file.

    Unlike most of our other component files, we are not using Typescript in Storybook, so it is not necessary to provide types within your story file.

    hashtag
    Data.js

    The data.js file is meant to provide mock data to your unit tests and Storybook files. It is ignored by Webpack, so don't include any objects or functions your component needs in order to render properly.

    The data file does not use Typescript since it is generally only used to provide mock JSON data.

    Perhaps the most frequent use case for this file is that you can copy/paste the data exactly as it comes from Sitecore. Since Storybook files and unit tests exist in isolation, they won't receive Sitecore data. But your React component will be relying on data to render correctly. So the dummy data placed within the data file ensures your React component will still have similar data in the same shape as the Sitecore data it would have in real-world conditions. Thus, even in isolation, you can ensure your component looks and behaves in a consistent and predictable manner.

    On occasion, you may encounter a situation where you have a hard-coded object or map that is used for reference within your app. While these can be useful, sometimes they may clutter up your main index.tsx file. In this case, you may be tempted to house those values in the data.js file and import them into your React component from there. But as was mentioned above, these files are ignored by Webpack, so any necessary additional data you would want to exist in a file should be added to a file named something other than data.js.

    Depending on the complexity of your component, this file may not be necessary. For example, if your React component doesn't get called from Sitecore, doesn't have JSS fields, or it doesn't require any extra hard-coded data, then you will not need a data file.

    hashtag
    The folder

    Of course, all of these files must live somewhere, and that place is in a folder. This folder should of course be named after your component and added to the /components directory. Resist the temptaion to take creative liberties with the name of the folder, as this will be the folder that will be imported into the Component Factory (see /src/temp/componentFactory.js) and importing a folder named TheGreatestComponentYouHaveEverFeastedYourMiserableEyeballsOn for the Modal component won't make a lot of sense out of context, no matter how fitting it seemed in the moment.

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

  • This page calls the <SingleStepForm /> component in the layout json from Sitecore which have fields that look like this

  • <SingleStepForm /> gets rendered via its composition file converts this JSS fields object as its props.

  • <SingleStepForm /> checks to make sure that modelJson.value exists before parsing it from a string to actual json, creating the variable formModel.

    • fields.ModelJson ie: formModel contains data for every field and their props. It's an array of objects where each object contains information about that field. After parsing the JSS, formModel looks like this: (note - only a few fields are shown as an example here)

  • formModel then gets passed into createFormInit() to process all of this data into something more concise and manageable

  • const createdFields = useMemo(() => createFormInit(formModel, false), []);

    • The formModel array will get passed into parseFields()

    • parseFields() will then run each of the form objects that are inside the formModel through yet another and final parser, createForm(). It will then save these objects to a new array and filter out any nulls generated by createForm() and return this array back to <SingleStepForm />

    • This function uses a Typescript overload to determine the correct return type depending on which multi boolean is passed in

    \

    hashtag
    Side note: Important info

    1. Since there is a lot of very important information missing from the fields.ModelJson, (input type, hidden fields, etc) from Sitecore we need a way to dynamically figure all of this out the best we can, with what we've been given. This is where mapping.ts comes in. This file contains 3 sections:

      • inputMap

        • inputMap is an object with field names as a key and their dataMap type as the value. These are basically fields that we've determined to be non-input type fields, such as select, checkbox, heading, etc... or fields that are hidden or unimportant

    const inputMap: CFMappingType["inputMap"] = { alternatephone: "phone", "alternate_phone_#": "phone", captcha: "recaptcha", checkbox: "checkbox", checkbox_list: "checkboxGroup",

    // ... };

    • regexMap

      • regexMap is an object with either a validationPattern name or input type as the key with the value being an object containing an error message and RegEx pattern to be used in validation

    const regexMap: CFMappingType["regexMap"] = { email: { message: "Not a valid email format", value: /^[^\s@]+@[^\s@]+.[^\s@]{2,}$/, }, lettersWhiteSpace: { message: "Can only be letters and spaces", value: /^[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ-]+(\s+[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ]+)*$/, }, notSameDigits: { message: "Can only contain numbers", value: /^(\d)\d*(?!\1)\d+$/, },

    // ... };

    const { file, props } = dataMap[inputType];

    const Component = loadable(() => import(src/components/Form/${file}));

    • Component: The React component that was dynamically imported

    • data: An object containing details about the field such as maxLength, minLength, placeholder, required, etc..

    • file: The name of the component that was dynamically imported

    • id: A unique id for each field since sitecore doesn't return usable id's

    • props: The props returned from the dataMap lookup along with the column width for each field

    • validations: Will either be null or an object that contains if it should show on the confirmation screen and the validationPattern if any

    1. <SingleStepForm /> then iterates through the createdFields array and will:

      • pass the props.columns value to <FieldWrapper /> to determine how wide the field should be in CSS

      • inside <FieldWrapper /> it will nest the dynamic <Component /> that was returned and will pass in some props, those are:

        • data: The data object returned from createForm()

        • errors: This is an object created by react-hook-form

    hashtag
    Inside the FormField

    1. Inside the /components/Form folder are all of the FormField components. These components are the default exports from each of these files such as <FormInput />

      • The purpose of these components is to take the createForm() generated props that were passed in via the <SingleStepForm /> and to 'normalize' them into simpler generic props for the <Input /> for example. Each file will have a Form[Input] component with its related Input component. This Input component will then be used in tests and storybook stories.

    const FormInput = ({ data, errors, name, props, register, validations, }: FormInputProps) => {

    const { mask, type } = props || {};

    const { label = "", maxLength, required, toolTipText } = data;

    const propData = { error: { hasError: Boolean(errors[name]?.message), errorMessage: errors[name]?.message, }, id: label, label, mask, maxLength, name, register, required, type, validations, };

    hashtag
    MultiStepForm

    The <MultiStepForm /> is nearly identical to <SingleStepForm /> except that it has multiple steps to the form. Like <SingleStepForm />, the fields data will also get pushed to createFormInit() and from there to multiStepFormFields(). It's here that the fields will be parsed through parsedFields() and then afterwards they will be separated into an array that contains the <FormStepper /> fields first and then the fields for the first screen, second screen and so on. This array will look something like this:

    When this array is finally returned back to <MultiStepForm>, the form stepper data is parsed out and passed into the <FormStepper /> component and the first array of field objects in the remaining array will be rendered out.

    We need to map through the outer array first and then loop through each of the inner array's to render each of the fields. The fields that are not shown on the current step will get a class added to FieldWrapper as hidden, all of the other fields will get the class, block. Since react-hook-form tracks our inputs via a ref, its much easier to just build out all of the steps of the form at once and just hide/show the active fields via CSS. This way we don't lose the ref when the fields get mounted/unmounted from the DOM.

    We also need to account for required fields that are now 'hidden' due to not being contained in the activeStep. This will cause an error in the browser saying that a required field is not focusable. We get around that by toggling the 'required' prop for that field depending on whether or not its currently in the activeStep

    React | Typescript | Tailwind | Forms | Unit Tests

    Reportsarrow-up-right
    Issuesarrow-up-right
    Estimatesarrow-up-right
    Componentsarrow-up-right
    Delete this attachmentarrow-up-right
    Screen Shot 2022-03-28 at 4.36.23 PM.pngarrow-up-right
    Historyarrow-up-right
    Activityarrow-up-right
    Time In Statusarrow-up-right
    2 pull requestsarrow-up-right
    Create brancharrow-up-right
    Story - Created by Jira Software - do not edit or delete. Issue type for a user story.
    Medium - Has the potential to affect progress.
    Medium - Has the potential to affect progress.
    call the Help Deskarrow-up-right

    Tailwind CSS

    Tailwind CSS is a utility-based styling library. In order to streamline and standardize things like colors and spacing within our application, an Electron theme has been created to extend Tailwind's functionality, thus making it easy for us to access some standard Duke colors, fonts, etc. As a result, you will get most of the magic of Tailwind, but with most of the colors, text, and sizing options overwritten to reflect Duke's design system.

    We won't talk too much about the decisions behind why we are using this implementation here, but Chris Greufe has already done an incredible job documenting the ins-and-outs of it [here](https: //electron.duke-energy.com/foundation/utilities/utility-first). If you want to know more of the granular aspects and philosophy to our approach, you are encouraged to give it a read.

    Instead, this doc will mostly focus on how the dev team actually uses this implementation and a bit about our approach, as well as a proposed style guide.

    hashtag
    Philosophy and Approach

    Tailwind CSS is our primary way of styling within the DXT-JSS-Public React App. A lot of effort and resources have been put into making Tailwind and Electron do as much of the heavy lifting for us as possible, so for maintainability's sake, every effort should be made to find a solution to style your work with Tailwind. If you're hitting a wall trying to figure out an approach that works within Tailwind, don't hesitate to reach out to a teammate. If you find yourself in the rare situation where you encounter something that simply cannot be resolved using Tailwind, we use [Styled Components](https: //styled-components.com) as our fallback. If you find that you are creating a styled component often to deal with an edge case, it's probably worth documenting [here](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests).

    hashtag
    Tooling

    There's not a lot of tooling required for Tailwind but [the Tailwind CSS Intellisense VSCode plugin](https: //marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) can be pretty helpful. Once installed, it gives suggestions for Tailwind classes as well as Electron-specific ones. It also shows a preview of the CSS rules that the utility class utilizes.

    hashtag
    Enabling Tailwind Properties

    Although Tailwind provides the flexibility to apply a wide range of modifiers (called variants in Tailwind) to a variety of CSS rules, you may occasionally find that a Tailwind class is not behaving the way you expect in a responsive context, or upon hover. This may mean that you will need to edit the Tailwind config. [You can find more info about that here.](https: //tailwindcss.com/docs/configuring-variants) Please be careful to [extend the values](https: //v1.tailwindcss.com/docs/configuring-variants), rather than simply adding them to the variant object, which will overwrite all the defaults.

    hashtag
    Key Differences

    The main difference you'll find between Tailwind's approach and Electron's is that Duke doesn't need an extensive color library. As a result, you'll find that something like text-blue-800 or bg-yellow-200 does not behave as you'd expect. Most likely you will be looking for something like text-blue-dark or bg-yellow. So the color palette will be limited, and rather than a number to modify the color, there will either be no modifier, or a modifier of -light, -lighter, -dark, or -darker.

    hashtag
    Style Guide

    Because almost all of our styles exist within utility classes, there is often no need for traditional CSS classes to style a block. It's fairly unusual to need to add a class like wrapper or large BEM classes. Occasionally, you may need to add a class to make it easier for unit tests to search for a selector. In such a case, we suggest that you use a js- prefix, and that you place it at the beginning of your utility classes.

    example:

    Often, the amount of classes you need to style a complex element can be rather long. In this case, it is suggested that you group your classes conceptually. Since Tailwind is a mobile-first framework, it makes sense to start with "base" styles that will be present across all sizes of the component, immediately followed by the responsive counterparts, in ascending order (md, lg, xl, etc).

    Of the base styles, start with sizing (height, width, padding, margin) and other fiddly rules so that they are easily accessible to you and anyone who may need to maintain your code in the future. Utility classes that represent rules that are easily identifiable at a glance, such as text color or background color, should come secondary.

    That was a lot of words, so let's look at an example.

    🚫 Bad

    ✅ Good

    That may seem a lot to unpack, so let's examine that for a second. Note that the classes start with the width property and then are immediately followed by the responsive variant. This pattern follows as we move through the margin and into the padding, which is then followed by the display types, and onto more obvious styles like font and background colors, which don't require a check of the code in order to verify the values. Finally, the rules end with the transition and duration properties, which are often "set and forget" rules.

    hashtag
    Resources

    • [Styled Components](https: //styled-components.com) - documentation in case you need to step outside the Tailwind garden

    • [Tailwind Requests](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests) - add to the Tailwind/Electron wishlist!

    • [Electron Docs](https: //electron.duke-energy.com/foundation/utilities/utility-first)

    React | Typescript | Tailwind | Forms | Unit Tests

    Storybook

    Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

    It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

    To get it up and running navigate to the app repo and, from the command line, enter:

    Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

    hashtag
    Stories

    A story, much like a React component, is a function that describes how to render a component.

    You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

    For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

    So here's how the initial default PushDownPanel might look:

    And then we can add variations based on different states, for example a version that uses icons:

    And the new stories will show up in the sidebar navigation, like so:

    hashtag
    Args

    Story definitions can be further improved to take advantage of Storybook's "args" concept.

    A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

    Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

    hashtag
    Controls

    Controls allow designers and developers to explore component behavior by mucking about with its arguments.

    Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

    This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

    Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

    A select menu to control the number of items that render:

    And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

    hashtag
    A11y and Other Addons

    Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

    One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

    We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

    hashtag
    Anatomy of a Story

    Stories exist alongside the other component files as stories.js.

    Here's an example of how a typical story might take shape:

    Let's break this down line by line:

    Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

    Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

    Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

    It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

    Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

    We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

    In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

    Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

    We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

    And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

    hashtag
    storybookCustomArgs

    The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

    We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

    hashtag
    createStoryOptions

    Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

    Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

    hashtag
    Resources

    • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

    • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

    • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

    Typescript Interfaces

    When we talk about a type in TypeScript, we mean a collection of things that you can do with a variable (or expression). You might be able to read or write a given property, call a function, use the expression as a constructor, or index into the object. Some objects (like Date) in JavaScript can do nearly all of those! In TypeScript, interfaces are the most flexible way of describing types.

    You'll see interfaces used to describe existing JavaScript APIs, create shorthand names for commonly-used types, constrain class implementations, describe array types, and more. While they don't generate any code (and thus have no runtime cost!), they are often the key point of contact between any two pieces of TypeScript code, especially when working with existing JavaScript code or built-in JavaScript objects.

    The only job of an interface in TypeScript is to describe a type. While class and function deal with implementation, interface helps us keep our programs error-free by providing information about the shape of the data we work with. Because the type information is erased from a TypeScript program during compilation, we can freely add type data using interfaces without worrying about the runtime overhead.

    While that sounds like a simple, one-purpose task, interfaces role in describing types becomes manifest in a large variety of ways. Let's look at some of them and how they can be used in TypeScript programs.

    hashtag
    Basics

    To define an interface in TypeScript, use the interface keyword:

    This defines a type, Greetable, that has a member function called greet that takes a string argument. You can use this type in all the usual positions; for example in a parameter type annotation. Here we use that type annotation to get type safety on the g parameter:

    When this code compiles, you won't see any mention of Greetable in the JavaScript code. Interfaces are only a compile-time construct and have no effect on the generated code.

    hashtag
    Interfaces: TypeScript's Swiss Army Knife

    Interfaces get to play a lot of roles in TypeScript code. We'll go into more detail on these after a quick overview.

    Describing an Object

    Many JavaScript functions take a "settings object". For example, jQuery's $.ajax takes an object that can have up to several dozen members that control its behavior, but you're only likely to pass a few of those in any given instance. TypeScript interfaces allow optional properties to help you use these sorts of objects correctly.

    Describing an Indexable Object

    JavaScript freely mixes members (foo.x) with indexers (foo['x']), but most programmers use one or the other as a semantic hint about what kind of access is taking place. TypeScript interfaces can be used to represent what the expected type of an indexing operation is.

    Ensuring Class Instance Shape

    Often, you'll want to make sure that a class you're writing matches some existing surface area. This is how interfaces are used in more traditional OOP languages like C# and Java, and we'll see that TypeScript interfaces behave very similarly when used in this role.

    Ensuring the Static Shape of a Class or constructor Object

    Interfaces normally describe the shape of an instance of a class, but we can also use them to describe the static shape of the class (including its constructor function). We'll cover this in a later post.

    hashtag
    Describing an Object

    You can also use interfaces to define the shape of objects that will typically be expressed in an object literal. Here's an example:

    Describing Simple Types

    Note the use of the ? symbol after some of the names. This marks a member as being optional. This lets callers of createButton supply only the members they care about, while maintaining the constraint that the required parts of the object are present:

    You typically won't use optional members when defining interfaces that are going to be implemented by classes.

    Here's another example that shows an interesting feature of types in TypeScript:

    Note that we didn't annotate pt in any way to indicate that it's of type Point. We don't need to, because type checking in TypeScript is structural: types are considered identical if they have the same surface area. Because pt has at least the same members as Point, it's suitable for use wherever a Point is expected.

    Describing External Types

    Interfaces are also used to describe code that is present at runtime, but not implemented in the current TypeScript project. For example, if you open the lib.d.ts file that all TypeScript projects implicitly reference, you'll see an interface declaration for Number:

    Now if we have an expression of type Number, the compiler knows that it's valid to call toPrecision on that expression.

    Extending Existing Types

    Moreover, interfaces in TypeScript are open, meaning you can add your own members to an interface by simply writing another interface block. If you have an external script that adds members to Date, for example, you simply need to write interface Date { /*...*/ } and declare the additional members.*

    * Note: There are some known issues with the Visual Studio editor that currently prevent this scenario from working as intended. We'll be fixing this limitation in a later release.

    hashtag
    Describing an Indexable Object

    A common pattern in JavaScript is to use an object (e.g. {}) as way to map from a set of strings to a set of values. When those values are of the same type, you can use an interface to describe that indexing into an object always produces values of a certain type (in this case, Widget).

    hashtag
    Ensuring Class Instance Shape

    Let's extend the Greetable example above:

    We can implement this interface in a class using the implements keyword:

    Now we can use an instance of Person wherever a Greetable is expected:

    var g: Greetable = new Person();

    Similarly, we can take advantage of the structural typing of TypeScript to implement Greetable in an object literal:

    Typescript

    In typescript global types can be declared in a .d.ts file and used anywhere without explicitly importing them. Our project's .d.ts file is named project.d.ts .

    It contains:

    1. Some library types in the form of [triple slash directives](https: //www.typescriptlang.org/docs/handbook/triple-slash-directives.html). These need to be placed at the top of the file.

    2. Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).

    3. Our own global types.

    Typescript provides many [Utility Types](https: //www.typescriptlang.org/docs/handbook/utility-types.html) which are useful for manipulating the base types in the global ComponentTypes interface.

    A few basic ones to know:

    hashtag
    Pick<Type, Keys>

    Only use the specified Keys from the Type.

    hashtag
    Partial<Type>

    Allows the type to be optional (undefined)

    hashtag
    Required<Type>

    Opposite of Partial, the type must be defined

    Using the stategies above you can select types from the global source and compose them to create a representation of the props in a specific component. While the global types live in project.d.ts , component level types should generally be placed in a types.ts file within the component directory and imported for use.

    Although ComponentTypes is a ✅ _Good starting place, some components may require a type that is more specific and not usefully included in the global declaration._


    hashtag
    Naming

    • {['class', 'enum', 'interface', 'namespace', 'type', 'variable-and-function'].map(item => (

    • {item.split('-').join(' ')}

    • ))}


    hashtag
    class

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers/methods use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    enum

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    interface

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    namespace

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    type

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ ✅ Good


    hashtag
    variable and function

    🐪 camelCase

    🚫 Bad

    ✅ Good


    React | Typescript | Tailwind | Forms | Unit Tests

    Typescript Types

    hashtag
    Everyday Types

    In this chapter, we'll cover some of the most common types of values you'll find in JavaScript code, and explain the corresponding ways to describe those types in TypeScript. This isn't an exhaustive list, and future chapters will describe more ways to name and use other types.

    Types can also appear in many more places than just type annotations. As we learn about the types themselves, we'll also learn about the places where we can refer to these types to form new constructs.

    We'll start by reviewing the most basic and common types you might encounter when writing JavaScript or TypeScript code. These will later form the core building blocks of more complex types.

    hashtag
    The primitives:string,number, andboolean

    JavaScript has three very commonly used [primitives](https: //developer.mozilla.org/en-US/docs/Glossary/Primitive): string, number, and boolean. Each has a corresponding type in TypeScript. As you might expect, these are the same names you'd see if you used the JavaScript typeof operator on a value of those types:

    • string represents string values like "Hello, world"

    • number is for numbers like 42. JavaScript does not have a special runtime value for integers, so there's no equivalent to int or float - everything is simply number

    The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

    hashtag
    Arrays

    To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on). You may also see this written as Array<number>, which means the same thing. We'll learn more about the syntax T<U> when we cover generics.

    Note that [number] is a different thing; refer to the section on [Tuples](https: //www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types).

    hashtag
    any

    TypeScript also has a special type, any, that you can use whenever you don't want a particular value to cause typechecking errors.

    When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically legal:

    The any type is useful when you don't want to write out a long type just to convince TypeScript that a particular line of code is okay.

    hashtag
    noImplicitAny

    When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any.

    You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag [noImplicitAny](https: //www.typescriptlang.org/tsconfig#noImplicitAny) to flag any implicit any as an error.

    hashtag
    Type Annotations on Variables

    When you declare a variable using const, var, or let, you can optionally add a type annotation to explicitly specify the type of the variable:

    TypeScript doesn't use "types on the left"-style declarations like int x = 0; Type annotations will always go after the thing being typed.

    In most cases, though, this isn't needed. Wherever possible, TypeScript tries to automatically infer the types in your code. For example, the type of a variable is inferred based on the type of its initializer:

    For the most part you don't need to explicitly learn the rules of inference. If you're starting out, try using fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully understand what's going on.

    hashtag
    Functions

    Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the types of both the input and output values of functions.

    hashtag
    Parameter Type Annotations

    When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function accepts. Parameter type annotations go after the parameter name:

    When a parameter has a type annotation, arguments to that function will be checked:

    Even if you don't have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments.

    hashtag
    Return Type Annotations

    You can also add return type annotations. Return type annotations appear after the parameter list:

    Much like variable type annotations, you usually don't need a return type annotation because TypeScript will infer the function's return type based on its return statements. The type annotation in the above example doesn't change anything. Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.

    hashtag
    Anonymous Functions

    Anonymous functions are a little bit different from function declarations. When a function appears in a place where TypeScript can determine how it's going to be called, the parameters of that function are automatically given types.

    Here's an example:

    Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach function, along with the inferred type of the array, to determine the type s will have.

    This process is called contextual typing because the context that the function occurred within informs what type it should have.

    Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it does happen can help you notice when type annotations aren't needed. Later, we'll see more examples of how the context that a value occurs in can affect its type.

    hashtag
    Object Types

    Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any JavaScript value with properties, which is almost all of them! To define an object type, we simply list its properties and their types.

    For example, here's a function that takes a point-like object:

    Here, we annotated the parameter with a type with two properties - x and y - which are both of type number. You can use , or ; to separate the properties, and the last separator is optional either way.

    The type part of each property is also optional. If you don't specify a type, it will be assumed to be any.

    hashtag
    Optional Properties

    Object types can also specify that some or all of their properties are optional. To do this, add a ? after the property name:

    In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a runtime error. Because of this, when you read from an optional property, you'll have to check for undefined before using it.

    hashtag
    Union Types

    TypeScript's type system allows you to build new types out of existing ones using a large variety of operators. Now that we know how to write a few types, it's time to start combining them in interesting ways.

    hashtag
    Defining a Union Type

    The first way to combine types you might see is a union type. A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union's members.

    Let's write a function that can operate on strings or numbers:

    hashtag
    Working with Union Types

    It's easy to provide a value matching a union type - simply provide a type matching any of the union's members. If you have a value of a union type, how do you work with it?

    TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can't use methods that are only available on string:

    The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

    For example, TypeScript knows that only a string value will have a typeof value "string":

    Another example is to use a function like Array.isArray:

    Notice that in the else branch, we don't need to do anything special - if x wasn't a string[], then it must have been a string.

    Sometimes you'll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing:

    It might be confusing that a union of types appears to have the intersection of those types' properties. This is not an accident - the name union comes from type theory. The union number | string is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.

    hashtag
    Type Aliases

    We've been using object types and union types by writing them directly in type annotations. This is convenient, but it's common to want to use the same type more than once and refer to it by a single name.

    A type alias is exactly that - a name for any type. The syntax for a type alias is:

    You can actually use a type alias to give a name to any type at all, not just an object type. For example, a type alias can name a union type:

    Note that aliases are only aliases - you cannot use type aliases to create different/distinct "versions" of the same type. When you use the alias, it's exactly as if you had written the aliased type. In other words, this code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:

    hashtag
    Interfaces

    An interface declaration is another way to name an object type:

    Just like when we used a type alias above, the example works just as if we had used an anonymous object type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares that it has the expected properties. Being concerned only with the structure and capabilities of types is why we call TypeScript a structurally typed type system.

    hashtag
    Differences Between Type Aliases and Interfaces

    Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

    |

    Extending an interface

    |

    Extending a type via intersections

    | |

    Adding new fields to an existing interface

    |

    A type cannot be changed after being created

    |

    You'll learn more about these concepts in later chapters, so don't worry if you don't understand all of these right away.

    • Prior to TypeScript version 4.2, type alias names [may appear in error messages](https: //www.typescriptlang.org/play?#code/PTAEGEHsFsAcEsA2BTATqNrLusgzngIYDm+oA7koqIYuYQJ56gCueyoAUCKAC4AWHAHaFcoSADMaQ0PCG80EwgGNkALk6c5C1EtWgAsqOi1QAb06groEbjWg8vVHOKcAvpokshy3vEgyyMr8kEbQJogAFND2YREAlOaW1soBeJAoAHSIkMTRmbbI8e6aPMiZxJmgACqCGKhY6ABGyDnkFFQ0dIzMbBwCwqIccabcYLyQoKjIEmh8kwN8DLAc5PzwwbLMyAAeK77IACYaQSEjUWZWhfYAjABMAMwALA+gbsVjoADqgjKESytQPxCHghAByXigYgBfr8LAsYj8aQMUASbDQcRSExCeCwFiIQh+AKfAYyBiQFgOPyIaikSGLQo0Zj-aazaY+dSaXjLDgAGXgAC9CKhDqAALxJaw2Ib2RzOISuDycLw+ImBYKQflCkWRRD2LXCw6JCxS1JCdJZHJ5RAFIbFJU8ADKC3WzEcnVZaGYE1ABpFnFOmsFhsil2uoHuzwArO9SmAAEIsSFrZB-GgAjjA5gtVN8VCEc1o1C4Q4AGlR2AwO1EsBQoAAbvB-gJ4HhPgB5aDwem-Ph1TCV3AEEirTp4ELtRbTPD4vwKjOfAuioSQHuDXBcnmgACC+eCONFEs73YAPGGZVT5cRyyhiHh7AAON7lsG3vBggB8XGV3l8-nVISOgghxoLq9i7io-AHsayRWGaFrlFauq2rg9qaIGQHwCBqChtKdgRo8TxRjeyB3o+7xAA), sometimes in place of the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in error messages.

    • Type aliases may not participate [in declaration merging, but interfaces can](https: //www.typescriptlang.org/play?#code/PTAEEEDtQS0gXApgJwGYEMDGjSfdAIx2UQFoB7AB0UkQBMAoEUfO0Wgd1ADd0AbAK6IAzizp16ALgYM4SNFhwBZdAFtV-UAG8GoPaADmNAcMmhh8ZHAMMAvjLkoM2UCvWad+0ARL0A-GYWVpA29gyY5JAWLJAwGnxmbvGgALzauvpGkCZmAEQAjABMAMwALLkANBl6zABi6DB8okR4Jjg+iPSgABboovDk3jjo5pbW1d6+dGb5djLwAJ7UoABKiJTwjThpnpnGpqPBoTLMAJrkArj4kOTwYmycPOhW6AR8IrDQ8N04wmo4HHQCwYi2Waw2W1S6S8HX8gTGITsQA).

    For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.

    hashtag
    Type Assertions

    Sometimes you will have information about the type of a value that TypeScript can't know about.

    For example, if you're using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

    In this situation, you can use a type assertion to specify a more specific type:

    Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior of your code.

    You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:

    Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won't be an exception or null generated if the type assertion is wrong.

    TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents "impossible" coercions like:

    Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid. If this happens, you can use two assertions, first to any (or unknown, which we'll introduce later), then to the desired type:

    hashtag
    Literal Types

    In addition to the general types string and number, we can refer to specific strings and numbers in type positions.

    One way to think about this is to consider how JavaScript comes with different ways to declare a variable. Both var and let allow for changing what is held inside the variable, and const does not. This is reflected in how TypeScript creates types for literals.

    By themselves, literal types aren't very valuable:

    It's not much use to have a variable that can only have one value!

    But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:

    Numeric literal types work the same way:

    Of course, you can combine these with non-literal types:

    There's one more kind of literal type: boolean literals. There are only two boolean literal types, and as you might guess, they are the types true and false. The type boolean itself is actually just an alias for the union true | false.

    hashtag
    Literal Inference

    When you initialize a variable with an object, TypeScript assumes that the properties of that object might change values later. For example, if you wrote code like this:

    TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error. Another way of saying this is that obj.counter must have the type number, not 0, because types are used to determine both reading and writing behavior.

    The same applies to strings:

    In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of handleRequest which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.

    There are two ways to work around this.

    1. You can change the inference by adding a type assertion in either location:

    // Change 1: const req = { url: "https: //example.com", method: "GET" as "GET" }; // Change 2handleRequest(req.url, req.method as "GET");Try

    The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.

    hashtag
    nullandundefined

    JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.

    TypeScript has two corresponding types by the same names. How these types behave depends on whether you have the [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) option on.

    hashtag
    strictNullChecksoff

    With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) off, values that might be null or undefined can still be accessed normally, and the values null and undefined can be assigned to a property of any type. This is similar to how languages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a major source of bugs; we always recommend people turn [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on if it's practical to do so in their codebase.

    hashtag
    strictNullCheckson

    With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value. Just like checking for undefined before using an optional property, we can use narrowing to check for values that might be null:

    hashtag
    Non-null Assertion Operator (Postfix!)

    TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn't null or undefined:

    Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to only use ! when you know that the value can't be null or undefined.

    hashtag
    Enums

    Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it's a feature which you should know exists, but maybe hold off on using unless you are sure. You can read more about enums in the [Enum reference page](https: //www.typescriptlang.org/docs/handbook/enums.html).

    hashtag
    Less Common Primitives

    It's worth mentioning the rest of the primitives in JavaScript which are represented in the type system. Though we will not go into depth here.

    bigint

    From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt:

    You can learn more about BigInt in [the TypeScript 3.2 release notes](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#bigint).

    symbol

    There is a primitive in JavaScript used to create a globally unique reference via the function Symbol():

    You can learn more about them in [Symbols reference page](https: //www.typescriptlang.org/docs/handbook/symbols.html).

    Ideas

    [11:24 AM] Pope, Spencer O

    Heres the Search abstract,

    https://app.abstract.com/projects/ab3d66ba-f90d-437a-a2be-c3f0d7144933/branches/master/commits/12d33dccde490d0aaf31b2380ee3734eb2ed5deb/files/c0563358-566e-463f-9b32-fcfe73c25835/layers/D7677409-1CCF-4758-8431-81A7F657946Carrow-up-right

    \

    There was an idea for some type of autocomplete, but it was omitted from the MVP version we launched with. Early step in re-visiting this would be to 1. check that this type of thing is still desirable, 2. research what is possible with the SOLR search platform

    \

    like 1

    Abstract

    \

    Types VS Interfaces

    hashtag
    Object Types

    In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types.

    As we've seen, they can be anonymous:

    or they can be named by using either an interface

    or a type alias.

    In all three examples above, we've written functions that take objects that contain the property name (which must be a string) and age (which must be a number).

    hashtag
    Property Modifiers

    Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.

    hashtag
    Optional Properties

    Much of the time, we'll find ourselves dealing with objects that might have a property set. In those cases, we can mark those properties as optional by adding a question mark (?) to the end of their names.

    In this example, both xPos and yPos are considered optional. We can choose to provide either of them, so every call above to paintShape is valid. All optionality really says is that if the property is set, it better have a specific type.

    We can also read from those properties - but when we do under [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks), TypeScript will tell us they're potentially undefined.

    In JavaScript, even if the property has never been set, we can still access it - it's just going to give us the value undefined. We can just handle undefined specially.

    Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.

    Here we used [a destructuring pattern](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) for paintShape's parameter, and provided [default values](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Default_values) for xPos and yPos. Now xPos and yPos are both definitely present within the body of paintShape, but optional for any callers to paintShape.

    Note that there is currently no way to place type annotations within destructuring patterns. This is because the following syntax already means something different in JavaScript.

    In an object destructuring pattern, shape: Shape means "grab the property shape and redefine it locally as a variable named Shape. Likewise xPos: number creates a variable named number whose value is based on the parameter's xPos.

    Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove optional attributes.

    hashtag
    readonlyProperties

    Properties can also be marked as readonly for TypeScript. While it won't change any behavior at runtime, a property marked as readonly can't be written to during type-checking.

    Using the readonly modifier doesn't necessarily imply that a value is totally immutable - or in other words, that its internal contents can't be changed. It just means the property itself can't be re-written to.

    It's important to manage expectations of what readonly implies. It's useful to signal intent during development time for TypeScript on how an object should be used. TypeScript doesn't factor in whether properties on two types are readonly when checking whether those types are compatible, so readonly properties can also change via aliasing.

    Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove readonly attributes.

    hashtag
    Index Signatures

    Sometimes you don't know all the names of a type's properties ahead of time, but you do know the shape of the values.

    In those cases you can use an index signature to describe the types of possible values, for example:

    Above, we have a StringArray interface which has an index signature. This index signature states that when a StringArray is indexed with a number, it will return a string.

    An index signature property type must be either ‘string' or ‘number'.

    chevron-rightIt is possible to support both types of indexers...hashtag

    While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name's type does not match the string index's type, and the type checker gives an error:

    However, properties of different types are acceptable if the index signature is a union of the property types:

    Finally, you can make index signatures readonly in order to prevent assignment to their indices:

    You can't set myArray[2] because the index signature is readonly.

    hashtag
    Extending Types

    It's pretty common to have types that might be more specific versions of other types. For example, we might have a BasicAddress type that describes the fields necessary for sending letters and packages in the U.S.

    In some situations that's enough, but addresses often have a unit number associated with them if the building at an address has multiple units. We can then describe an AddressWithUnit.

    This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress when our changes were purely additive. Instead, we can extend the original BasicAddress type and just add the new fields that are unique to AddressWithUnit.

    The extends keyword on an interface allows us to effectively copy members from other named types, and add whatever new members we want. This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related. For example, AddressWithUnit didn't need to repeat the street property, and because street originates from BasicAddress, a reader will know that those two types are related in some way.

    interfaces can also extend from multiple types.

    hashtag
    Intersection Types

    interfaces allowed us to build up new types from other types by extending them. TypeScript provides another construct called intersection types that is mainly used to combine existing object types.

    An intersection type is defined using the & operator.

    Here, we've intersected Colorful and Circle to produce a new type that has all the members of Colorful and Circle.

    hashtag
    Interfaces vs. Intersections

    We just looked at two ways to combine types which are similar, but are actually subtly different. With interfaces, we could use an extends clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias. The principle difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you'd pick one over the other between an interface and a type alias of an intersection type.

    hashtag
    Generic Object Types

    Let's imagine a Box type that can contain any value - strings, numbers, Giraffes, whatever.

    Right now, the contents property is typed as any, which works, but can lead to accidents down the line.

    We could instead use unknown, but that would mean that in cases where we already know the type of contents, we'd need to do precautionary checks, or use error-prone type assertions.

    One type safe approach would be to instead scaffold out different Box types for every type of contents.

    But that means we'll have to create different functions, or overloads of functions, to operate on these types.

    That's a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frustrating, since our box types and overloads are all effectively the same.

    Instead, we can make a generic Box type which declares a type parameter.

    You might read this as "A Box of Type is something whose contents have type Type". Later on, when we refer to Box, we have to give a type argument in place of Type.

    Think of Box as a template for a real type, where Type is a placeholder that will get replaced with some other type. When TypeScript sees Box<string>, it will replace every instance of Type in Box<Type> with string, and end up working with something like { contents: string }. In other words, Box<string> and our earlier StringBox work identically.

    Box is reusable in that Type can be substituted with anything. That means that when we need a box for a new type, we don't need to declare a new Box type at all (though we certainly could if we wanted to).

    This also means that we can avoid overloads entirely by instead using [generic functions](https: //www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions).

    It is worth noting that type aliases can also be generic. We could have defined our new Box<Type> interface, which was:

    by using a type alias instead:

    Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.

    We'll circle back to type aliases in just a little bit.

    hashtag
    TheArrayType

    Generic object types are often some sort of container type that work independently of the type of elements they contain. It's ideal for data structures to work this way so that they're re-usable across different data types.

    It turns out we've been working with a type just like that throughout this handbook: the Array type. Whenever we write out types like number[] or string[], that's really just a shorthand for Array<number> and Array<string>.

    Much like the Box type above, Array itself is a generic type.

    Modern JavaScript also provides other data structures which are generic, like Map<K, V>, Set<T>, and Promise<T>. All this really means is that because of how Map, Set, and Promise behave, they can work with any sets of types.

    hashtag
    TheReadonlyArrayType

    The ReadonlyArray is a special type that describes arrays that shouldn't be changed.

    Much like the readonly modifier for properties, it's mainly a tool we can use for intent. When we see a function that returns ReadonlyArrays, it tells us we're not meant to change the contents at all, and when we see a function that consumes ReadonlyArrays, it tells us that we can pass any array into that function without worrying that it will change its contents.

    Unlike Array, there isn't a ReadonlyArray constructor that we can use.

    Instead, we can assign regular Arrays to ReadonlyArrays.

    Just as TypeScript provides a shorthand syntax for Array<Type> with Type[], it also provides a shorthand syntax for ReadonlyArray<Type> with readonly Type[].

    One last thing to note is that unlike the readonly property modifier, assignability isn't bidirectional between regular Arrays and ReadonlyArrays.

    hashtag
    Tuple Types

    A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.

    Here, StringNumberPair is a tuple type of string and number. Like ReadonlyArray, it has no representation at runtime, but is significant to TypeScript. To the type system, StringNumberPair describes arrays whose 0 index contains a string and whose 1 index contains a number.

    If we try to index past the number of elements, we'll get an error.

    We can also [destructure tuples](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring) using JavaScript's array destructuring.

    Tuple types are useful in heavily convention-based APIs, where each element's meaning is "obvious". This gives us flexibility in whatever we want to name our variables when we destructure them. In the above example, we were able to name elements 0 and 1 to whatever we wanted.

    However, since not every user holds the same view of what's obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.

    Other than those length checks, simple tuple types like these are equivalent to types which are versions of Arrays that declare properties for specific indexes, and that declare length with a numeric literal type.

    Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (? after an element's type). Optional tuple elements can only come at the end, and also affect the type of length.

    Tuples can also have rest elements, which have to be an array/tuple type.

    • StringNumberBooleans describes a tuple whose first two elements are string and number respectively, but which may have any number of booleans following.

    • StringBooleansNumber describes a tuple whose first element is string and then any number of

    A tuple with a rest element has no set "length" - it only has a set of well-known elements in different positions.

    Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in [rest parameters and arguments](https: //www.typescriptlang.org/docs/handbook/2/functions.html#rest-parameters-and-arguments), so that the following:

    is basically equivalent to:

    This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don't want to introduce intermediate variables.

    hashtag
    readonlyTuple Types

    One final note about tuple types - tuples types have readonly variants, and can be specified by sticking a readonly modifier in front of them - just like with array shorthand syntax.

    As you might expect, writing to any property of a readonly tuple isn't allowed in TypeScript.

    Tuples tend to be created and left un-modified in most code, so annotating types as readonly tuples when possible is a good default. This is also important given that array literals with const assertions will be inferred with readonly tuple types.

    Here, distanceFromOrigin never modifies its elements, but expects a mutable tuple. Since point's type was inferred as readonly [3, 4], it won't be compatible with [number, number] since that type can't guarantee point's elements won't be mutated.

    Enums

    hashtag
    Enums

    Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.

    Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

    hashtag
    Numeric enums

    We'll first start off with numeric enums, which are probably more familiar if you're coming from other languages. An enum can be defined using the enum keyword.

    Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-incremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has 3 , and Right has 4 .

    If we wanted, we could leave off the initializers entirely:

    Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum.

    Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum:

    Numeric enums can be mixed in [computed and constant members (see below)](https: //www.typescriptlang.org/docs/handbook/enums.html#computed-and- constant-members). The short story is, enums without initializers either need to be first, or have to come after numeric enums initialized with numeric constants or other constant enum members. In other words, the following isn't allowed:

    hashtag
    String enums

    String enums are a similar concept, but have some subtle [runtime differences](https: //www.typescriptlang.org/docs/handbook/enums.html#enums-at-runtime) as documented below. In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member.

    While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serialize" well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the value is often opaque - it doesn't convey any useful meaning on its own (though [reverse mapping](https: //www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings) can often help). String enums allow you to give a meaningful and readable value when your code runs, independent of the name of the enum member itself.

    hashtag
    Heterogeneous enums

    Technically enums can be mixed with string and numeric members, but it's not clear why you would ever want to do so:

    Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised that you don't do this.

    hashtag
    Computed and

    constant members

    Each enum member has a value associated with it which can be either _ constant_ or computed. An enum member is considered constant if:

    • It is the first member in the enum and it has no initializer, in which case it's assigned the value 0:

    • The enum member is initialized with a constant enum expression. A constant enum expression is a subset of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant enum expression if it is:

      1. a literal enum expression (basically a string literal or a numeric literal)

      2. a reference to previously defined constant enum member (which can originate from a different enum)

    In all other cases enum member is considered computed.

    hashtag
    Union enums and enum member types

    There is a special subset of constant enum members that aren't calculated: literal enum members. A literal enum member is a constant enum member with no initialized value, or with values that are initialized to

    • any string literal (e.g. "foo", "bar, "baz")

    • any numeric literal (e.g. 1, 100)

    When all members in an enum have literal enum values, some special semantics come into play.

    The first is that enum members also become types as well! For example, we can say that certain members can only have the value of an enum member:

    The other change is that enum types themselves effectively become a union of each enum member. With union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values incorrectly. For example:

    In that example, we first checked whether x was not E.Foo . If that check succeeds, then our || will short-circuit, and the body of the ‘if' will run. However, if the check didn't succeed, then x can only be E.Foo , so it doesn't make sense to see whether it's equal to E.Bar .

    hashtag
    Enums at runtime

    Enums are real objects that exist at runtime. For example, the following enum

    can actually be passed around to functions

    hashtag
    Enums at compile time

    Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys as strings.

    hashtag
    Reverse mappings

    In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from enum values to enum names. For example, in this example:

    TypeScript compiles this down to the following JavaScript:

    In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and reverse ( value -> name ) mappings. References to other enum members are always emitted as property accesses and never inlined.

    Keep in mind that string enum members do not get a reverse mapping generated at all.

    hashtag
    `

    const` enums

    In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid paying the cost of extra generated code and additional indirection when accessing enum values, it's possible to use const enums. const enums are defined using the const modifier on our enums:

    const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. const enum members are inlined at use sites. This is possible since const enums cannot have computed members.

    in generated code will become

    ** const enum pitfalls**

    Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc --declaration transforms .ts files into .d.ts files.

    1. For the reasons laid out in the [isolatedModules documentation](https: //www.typescriptlang.org/tsconfig#references-to- const-enum-members), that mode is fundamentally incompatible with ambient const enums. This means if you publish ambient const enums, downstream consumers will not be able to use [isolatedModules](https: //www.typescriptlang.org/tsconfig#isolatedModules) and those enum values at the same time.

    2. You can easily inline values from version A of a dependency at compile time, and import version B at runtime. Version A and B's enums can have different values, if you are not very careful, resulting in [surprising bugs](https: //github.com/microsoft/TypeScript/issues/5219#issue-110947903), like taking the wrong branches of if statements. These bugs are especially pernicious because it is common to run automated tests at roughly the same time as projects are built, with the same dependency versions, which misses these bugs completely.

    Here are two approaches to avoiding these pitfalls:

    A. Do not use const enums at all. You can easily [ban const enums](https: //github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#how-can-i-ban-specific-language-feature) with the help of a linter. Obviously this avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining enums from other projects, inlining a project's own enums is not problematic and has performance implications. B. Do not publish ambient const enums, by de constifying them with the help of [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums). This is the approach taken internally by the [TypeScript project itself](https: //github.com/microsoft/TypeScript/pull/5422). [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums)emits the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from .d.ts files [in a build step](https: //github.com/microsoft/TypeScript/blob/1a981d1df1810c868a66b3828497f049a944951c/Gulpfile.js#L144).

    This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a project can still inline its own enums, unlike banning const enums entirely.

    hashtag
    Ambient enums

    Ambient enums are used to describe the shape of already existing enum types.

    One important difference between ambient and non-ambient enums is that, in regular enums, members that don't have an initializer will be considered constant if its preceding enum member is considered constant. By contrast, an ambient (and non- const) enum member that does not have an initializer is always considered computed.

    hashtag
    Objects vs Enums

    In modern TypeScript, you may not need an enum when an object with as const could suffice:

    \

    React

    hashtag
    Folder and File Structure

    Each component should have its own folder nested inside the /components folder. The name of this folder should match the name of the component itself. Inside this folder you should have the following files:

    • data.js

      • contains a copy of the JSS fields object for this component. This will get imported in unit tests and Storybook stories. If the component doesn't get called from Sitecore and doesn't have JSS fields, then this file isn't needed.

    • index.tsx

      • main React component

    • stories.js

      • Storybook stories

    • styles.ts(x) (optional)

      • if you use Styled Components for the component, they will live here

    • test.tsx

      • Jest unit tests

    • types.ts

      • Typescript types and interfaces the component

    \


    \

    hashtag
    Composition File

    Before working on the React component itself we first need to compose simpler props for it instead of parsing through the JSS fields object directly. The goal is to turn something like this:

    into this:

    The composition file is located at lib/composition/index.js and contains an exported function for each of the React components. The JSS fields object (and sometimes placeholder object) from Sitecore is passed in and destructured. Using the ComponentTypes global Typescript type both as a reference and consistent naming convention amongst all of the components, you will use these as keys to build your return object which will be passed in as the final props object to your component.

    In the following example, the QuickLinks component will get a single items prop returned to it, which will consist of an array of objects

    hashtag
    Optional Chaining

    An important thing to note and best practice is to use optional chaining when reaching into an object. The (?.) operator allows you to reach as far down into a nested object without first having to validate that the previous step in the chain is valid and exists first.

    [MDN - Optional Chaining](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)

    \


    \

    hashtag
    Function Components

    We should always use function components with hooks over class components. Function components are 'stateless' and simple as they don't track state or have lifecycle and render methods like class components do. If you need to track state in your component, import the useState hook.

    hashtag
    Use Short Syntax Over React Fragments

    If your return content doesn't have a wrapping div you will need to create a fragment by using a short syntax element <> rather than a React <Fragment>. The only exception is if you need to use a key when using a map. In these instances you will need to use a <Fragment> [React Short Syntax](https: //reactjs.org/docs/fragments.html#short-syntax)

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use Implicit Returns

    With ES6 and arrow functions you can omit the curly braces and the return keyword if your component is immediately returning something.

    🚫 Bad

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use PascalCase and Export At The End Of The File

    Component names should always be PascalCased and exported at the bottom of the file. If there is more than one component to export you need to declare which one is the default export:

    \


    \

    hashtag
    Spread and Destructure

    Where possible always destructure props and other objects/arrays where used. This will help in typing and keeping things clear and not hidden behind a root object. If you don't need to destructure out all of the properties of an object, use the ...rest keyword and pass that in instead like so:

    🚫 Bad

    ✅ Good

    If you're not transforming the props names from the parent to the child, then you can omit setting them directly on the child and instead just spread them in with the {...rest} property.

    \


    \

    hashtag
    Small, Focused and DRY

    Our components should be broken down into small, reusable functions instead of one gigantic component.

    🚫 Bad

    ✅ Good

    Each component should do only one thing and only be responsible for the logic for that one thing. If your function is growing in size, it's probably time to refactor it into smaller, more focused components. This will also help in making our components more reusable, consistent and TESTABLE.

    React | Typescript | Tailwind | Forms | Unit Tests

    Accessability

    Understanding Techniques for WCAG Success Criteria

    hashtag
    Introduction to Web Accessibility | Web Accessibility Initiative (WAI) | W3C

    Excerpt

    The Website of the World Wide Web Consortium’s Web Accessibility Initiative.


    Page Contents

    hashtag
    Accessibility in Context

    The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect.

    The Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.

    Thus the impact of disability is radically changed on the Web because the Web removes barriers to communication and interaction that many people face in the physical world. However, when websites, applications, technologies, or tools are badly designed, they can create barriers that exclude people from using the Web.

    Accessibility is essential for developers and organizations that want to create high-quality websites and web tools, and not exclude people from using their products and services.

    hashtag
    What is Web Accessibility

    Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can:

    • perceive, understand, navigate, and interact with the Web

    • contribute to the Web

    Web accessibility encompasses all disabilities that affect access to the Web, including:

    • auditory

    • cognitive

    • neurological

    Web accessibility also benefits people without disabilities, for example:

    • people using mobile phones, smart watches, smart TVs, and other devices with small screens, different input modes, etc.

    • older people with changing abilities due to ageing

    • people with “temporary disabilities” such as a broken arm or lost glasses

    For a 7-minute video with examples of how accessibility is essential for people with disabilities and useful for everyone in a variety of situations, see:

    hashtag
    Accessibility is Important for Individuals, Businesses, Society

    The Web is an increasingly important resource in many aspects of life: education, employment, government, commerce, health care, recreation, and more. It is essential that the Web be accessible in order to provide equal access and equal opportunity to people with diverse abilities. Access to information and communications technologies, including the Web, is defined as a basic human right in the United Nations Convention on the Rights of Persons with Disabilities (UN ).

    The Web offers the possibility of unprecedented access to information and interaction for many people with disabilities. That is, the accessibility barriers to print, audio, and visual media can be much more easily overcome through web technologies.

    Accessibility supports social inclusion for people with disabilities as well as others, such as:

    • older people

    • people in rural areas

    • people in developing countries

    There is also a strong business case for accessibility. As shown in the previous section, accessible design improves overall user experience and satisfaction, especially in a variety of situations, across different devices, and for older users. Accessibility can enhance your brand, drive innovation, and extend your market reach.

    Web accessibility is required by law in many situations.

    hashtag
    Making the Web Accessible

    Web accessibility depends on several components working together, including web technologies, web browsers and other "user agents", authoring tools, and websites.

    The W3C Web Accessibility Initiative () develops technical specifications, guidelines, techniques, and supporting resources that describe accessibility solutions. These are considered international standards for web accessibility; for example, WCAG 2.0 is also an ISO standard: ISO/IEC 40500.

    hashtag
    Making Your Website Accessible

    Many aspects of accessibility are fairly easy to understand and implement. Some accessibility solutions are more complex and take more knowledge to implement.

    It is most efficient and effective to incorporate accessibility from the very beginning of projects, so you don’t need go back and to re-do work.

    hashtag
    Evaluating Accessibility

    When developing or redesigning a website, evaluate accessibility early and throughout the development process to identify accessibility problems early, when it is easier to address them. Simple steps, such as changing settings in a browser, can help you evaluate some aspects of accessibility. Comprehensive evaluation to determine if a website meets all accessibility guidelines takes more effort.

    There are evaluation tools that help with evaluation. However, no tool alone can determine if a site meets accessibility guidelines. Knowledgeable human evaluation is required to determine if a site is accessible.

    hashtag
    Examples

    hashtag
    Alternative Text for Images

    Images should include (alt text) in the markup/code.

    If alt text isn’t provided for images, the image information is inaccessible, for example, to people who cannot see and use a screen reader that reads aloud the information on a page, including the alt text for the visual image.

    When equivalent alt text is provided, the information is available to people who are blind, as well as to people who turn off images (for example, in areas with expensive or low bandwidth). It’s also available to technologies that cannot see images, such as search engines.

    hashtag
    Keyboard Input

    Some people cannot use a mouse, including many older users with limited fine motor control. An accessible website does not rely on the mouse; it makes . Then people with disabilities can use that mimic the keyboard, such as speech input.

    hashtag
    Transcripts for Audio

    Just as images aren’t available to people who can’t see, audio files aren’t available to people who can’t hear. Providing a text transcript makes the audio information accessible to people who are deaf or hard of hearing, as well as to search engines and other technologies that can’t hear.

    It’s easy and relatively inexpensive for websites to provide transcripts. There are also that create text transcripts in HTML format.

    hashtag
    For More Information

    W3C WAI provides a wide range of resources on different aspects of web accessibility , , , . We encourage you to explore this website, or look through the list.

    hashtag

    Excerpt

    WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.


    WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.

    Specific guidance for authors and evaluators on meeting the WCAG success criteria is provided in techniques, which include code examples, resources, and tests. W3C's document is updated periodically, about twice per year, to cover more current best practices and changes in technologies and tools.

    The three types of guidance in are explained below:

    • Sufficient techniques

    • Advisory techniques

    • Failures

    Also explained below:

    • General and technology-specific techniques - which can be sufficient or advisory

    • Other techniques - beyond what is in W3C's published document

    • Technique tests

    provides related information, including on .

    hashtag
    Techniques are Informative

    Techniques are informative—that means they are not required. The basis for determining conformance to WCAG 2.1 is the success criteria from the WCAG 2.1 standard—not the techniques.

    Note 1: W3C cautions against requiring W3C's sufficient techniques. The only thing that should be required is meeting the WCAG 2.1 success criteria. To learn more, see:

    • in the WCAG 2 FAQ

    Note 2: uses the words "must" and "should" only to clarify guidance within the techniques, not to convey requirements for WCAG.

    hashtag
    Sufficient Techniques

    Sufficient techniques are reliable ways to meet the success criteria.

    • From an author's perspective: If you use the sufficient techniques for a given criterion correctly and it is for your users, you can be confident that you met the success criterion.

    • From an evaluator's perspective: If web content implements the sufficient techniques for a given criterion correctly and it is for the content's users, it conforms to that success criterion. (The converse is not true; if content does not implement these sufficient techniques, it does not necessarily fail the success criteria, as explained in below.)

    There may be other ways to meet success criteria besides the sufficient techniques in W3C's document, as explained in below. (See also above.)

    hashtag
    Numbered Lists, "AND"

    The W3C-documented sufficient techniques are provided in a numbered list where each list item provides a technique or combination of techniques that can be used to meet the success criterion. Where there are multiple techniques on a numbered list item connected by "AND" then all of the techniques must be used to be sufficient. For example, has: "G115: Using semantic elements to mark up structure AND H49: Using semantic markup to mark emphasized or special text (HTML)".

    hashtag
    Advisory Techniques

    Advisory techniques are suggested ways to improve accessibility. They are often very helpful to some users, and may be the only way that some users can access some types of content.

    Advisory techniques are not designated as sufficient techniques for various reasons such as:

    • they may not be sufficient to meet the full requirements of the success criteria;

    • they may be based on technology that is not yet stable;

    • they may not be in many cases (for example, assistive technologies do not work with them yet);

    Authors are encouraged to apply all of the techniques where appropriate to best address the widest range of users' needs.

    hashtag
    Failures

    Failures are things that cause accessibility barriers and fail specific success criteria. The documented failures are useful for:

    • Authors to know what to avoid,

    • Evaluators to use for checking if content does not meet WCAG success criteria.

    Content that has a failure does not meet WCAG success criteria, unless an alternate version is provided without the failure.

    If anyone identifies a situation where a documented failure is not correct, please so that it can be corrected or deleted as appropriate.

    hashtag
    General and Technology-specific Techniques

    General techniques describe basic practices that apply to all technologies. Technology-specific techniques apply to a specific technology.

    Some success criteria do not have technology-specific techniques and are covered only with general techniques. Therefore, both the general techniques and the relevant technology-specific techniques should be considered.

    Publication of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1 success criteria and conformance requirements. Developers need to be aware of the limitations of specific technologies and provide content in a way that is accessible to people with disabilities.

    hashtag
    Other Techniques

    In addition to the techniques in W3C's document, there are other ways to meet WCAG success criteria. W3C's techniques are not comprehensive and may not cover newer technologies and situations.

    Web content does not have to use W3C's published techniques in order to conform to WCAG 2.1.__(See also above.)

    Content authors can develop different techniques. For example, an author could develop a technique for HTML5, , or other new technology. Other organizations may develop sets of techniques to meet WCAG 2.1 success criteria.

    Any techniques can be sufficient if:

    • they satisfy the success criterion, and

    • all of the are met.

    hashtag
    Submitting Techniques

    The WCAG Working Group encourages people to submit new techniques so that they can be considered for inclusion in updates of the document. Please submit techniques for consideration using the .

    hashtag
    Testing Techniques

    Each technique has tests that help:

    • authors verify that they implemented the technique properly, and

    • evaluators determine if web content meets the technique.

    The tests are only for a technique, they are not tests for conformance to WCAG success criteria.

    • Failing a technique test does not necessarily mean failing WCAG, because the techniques are discrete (that is, they address one specific point) and they are not required.

    • Content can meet WCAG success criteria in different ways other than W3C's published sufficient techniques.

    • Content that passes the sufficient techniques for a specific technology does not necessarily meet all WCAG success criteria. Some success criteria have only general techniques, not technology-specific techniques.

    Thus while the techniques are useful for evaluating content, evaluations must go beyond just checking the sufficient technique tests in order to evaluate how content conforms to WCAG success criteria.

    Failures are particularly useful for evaluations because they do indicate non-conformance (unless an alternate version is provided without the failure).

    hashtag
    User Agent and Assistive Technology Support Notes

    Some techniques require that web content users have specific browsers or assistive technologies in order for the technique to be . The User Agent and Assistive Technology Support Notes sections of individual techniques include some information to help determine accessibility support.

    hashtag
    Support Notes Change Over Time

    As time passes, the versions of user agents (browsers, etc.) or assistive technologies listed may not be the current versions. The Working Group may not update most of these notes as new versions are released. Authors should test techniques with the user agents and assistive technologies currently available to their users. See also .

    hashtag
    Using the Techniques

    is not intended to be used as a stand-alone document. Instead, it is expected that content authors will usually use to read the WCAG success criteria, and follow links from there to specific topics in Understanding WCAG 2.1 and to specific techniques.

    hashtag
    Alternatives must meet success criteria

    Some techniques describe how to provide alternate ways for users to get content. For example, mentions a transcript as an alternative for an audio file. Some alternatives must also conform to WCAG. For example, the transcript itself must meet all relevant success criteria.

    hashtag
    Example Code

    The code examples in the techniques are intended to demonstrate only the specific point discussed in the technique. They might not demonstrate best practice for other aspects of accessibility, usability, or coding not related to the technique. They are not intended to be copied and used as the basis for developing web content.

    Many techniques point to "working examples" that are more robust and may be appropriate for copying and integrating into web content.

    hashtag

    Sitecore Config

    The purpose of this spreadsheet is to allow values to be changed in lower environments. After EVERY deployment, the lower environments (Test, QA, etc) have their master databases restored from Production. That is great news that most of the conten is fresh but one issue is that any values that are hard coded for production, are also hard coded for the lower environments. There needs to be a way to modify these "production" values to their lower environment equivalents. That is where this spreadsheet comes in. This spreadsheet is used against each new database restore to update the production values to their lower environmetn equivalents.

    Item - The full sitecore path to the item that needs to be modified.

    GUID - The GUID to the item that needs to be modified.

    Typescript

    hashtag
    Global types

    In typescript global types can be declared in a .d.ts file and used anywhere without explicitly importing them. Our project's .d.ts file is named project.d.ts.

    chevron-rightproject.d.tshashtag

    It contains:

    1. Some library types in the form of [triple slash directives](https: //www.typescriptlang.org/docs/handbook/triple-slash-directives.html). These need to be placed at the top of the file.

    2. Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).

    3. Our own global types.

    Typescript provides many [Utility Types](https: //www.typescriptlang.org/docs/handbook/utility-types.html) which are useful for manipulating the base types in the global ComponentTypes interface.

    chevron-rightUtility Typeshashtag

    TypeScript: Documentation - Utility Types

    Excerpt

    Types which are globally included in TypeScript


    TypeScript provides several utility types to facilitate common type transformations. These utilities are available globally.

    A few basic ones to know:

    hashtag
    Pick<Type, Keys>

    Only use the specified Keys from the Type.

    hashtag
    Partial<Type>

    Allows the type to be optional (undefined)

    hashtag
    Required<Type>

    Opposite of Partial, the type must be defined

    Using the stategies above you can select types from the global source and compose them to create a representation of the props in a specific component. While the global types live in project.d.ts, component level types should generally be placed in a types.ts file within the component directory and imported for use.

    Although ComponentTypes is a ✅ _Good starting place, some components may require a type that is more specific and not usefully included in the global declaration._


    hashtag
    Naming


    hashtag
    class

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers/methods use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    enum

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    interface

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    namespace

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    type

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ ✅ Good


    hashtag
    variable and function

    🐪 camelCase

    🚫 Bad

    ✅ Good


    React | Typescript | Tailwind | Forms | Unit Tests

    Focus Order

    hashtag
    Understanding Success Criterion 2.4.3: Focus Order

    Excerpt

    The intent of this Success Criterion is to ensure that when users navigate sequentially through content, they encounter information in an order that is consistent with the meaning of the content and can be operated from the keyboard. This reduces confusion by letting users form a consistent mental model of the content. There may be different orders that reflect logical relationships in the content. For example, moving through components in a table one row at a time or one column at a time both reflect the logical relationships in the content. Either order may satisfy this Success Criterion.

    Unknown Prop Warning

    The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.

    There are a couple of likely reasons this warning could be appearing:

    1. Are you using {...this.props} or cloneElement(element, this.props)? Your component is transferring its own props directly to a child element (eg. transferring props). When transferring props to a child component, you should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.

    Don't Call PropTypes Warning

    Note:

    React.PropTypes has moved into a different package since React v15.5. Please use .

    We provide a codemod script to automate the conversion.

    In a future major release of React, the code that implements PropType validation functions will be stripped in production. Once this happens, any code that calls these functions manually (that isn't stripped in production) will throw an error.

    React Element Factories and JSX Warning

    You probably came here because your code is calling your component as a plain function call. This is now deprecated:

    hashtag
    JSX

    React components can no longer be called directly like this. Instead you can use JSX.

    const componentAlias = {
      HeroCarousel: "Hero",
      HeroCarouselNocache: "Hero",
      JssAccordion: "Accordion",
    
      // ...
    };
    <Component title={fields.PodItemLink?.value?.text} />
    <Component title={title} />
    // composition/index.js
    
    const QuickLinks = ({ fields }) => {
      const items = fields?.QuickLinkItems?.reduce(
        (acc, curr) => [
          ...acc,
          {
            image: { ...curr.Icon?.value },
            link: curr.Link?.value?.href,
            title: curr.Title?.value,
            id: curr.Id,
          },
        ],
        []
      );
      return { items };
    };
    // components/QuickLinks/index.tsx
    
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    {
      componentName: 'ContentMain',
      content: [{
          uid: '',
          componentName: 'Single Step Form',
          dataSource: '',
          fields: {
              ModelJson: {
                  value: '[ { "title":"Form Name", "fields":{ "Name":{ "label":"Form Name", "type":"input", "value":"Form Name", "name":"Name" }, "Id":{ "label":"Id", "type":"hidden", "value":"", "name":"Id" } }, "fresh":true }, { "title":"Text Input", "fields":{ "FormId":{ "label":"FormId", "type":"hidden", "value":"stepOne", "name":"FormId" }, "Name":{ "label":"Name", "type":"hidden", "value":"textinput", "name":"Name" }, "Id":{ "label":"Id", "type":"input", "value":"fixtureName", "name":"Id" }, "Label":{ "label":"Label", "type":"input", "value":"Fixture Selection", "name":"Label" }, "BackEndLabel":{ "label":"BackEnd Label", "type":"input", "value":"Fixture Selection", "name":"BackEndLabel" }, "Value":{ "label":"Default Value", "type":"input", "value":"", "name":"Value" }, "DefaultValueSource":{ "label":"Default Value Source", "type":"select", "value":[ { "value":"None", "selected":false, "label":"None" }, { "value":"cookie", "selected":true, "label":"Cookie" } ], "name":"DefaultValueSource" }, "DefaultValueKey":{ "label":"Default Value Source Key", "type":"input", "value":"fixtureName", "name":"DefaultValueKey"}]
              }
          }
      }]
    }
    - `createdFields` is memoized to cache the original value and keep it from re-rendering each cycle
    - The `false` parameter signifies that its not to return a multidimensional array, just a single array
    
    5. `createFormInit()` is a 'factory' for both `<SingleStepForm />` and `<MultiStepForm />`. We will focus on what happens if this is called from `<SingleStepForm />`.
    
    ```typescript
    
    const createFormInit: {
      (arr: Array<ParsedFormModel>, multi: true): CFReturnType[][];
      (arr: Array<ParsedFormModel>, multi: false): CFReturnType[];
    } = (arr: Array<ParsedFormModel>, multi: boolean): any => {
      return multi ? multiStepFormFields(arr) : parseFields(arr);
    };
    const parseFields = (arr: Array<ParsedFormModel>) => {
    
    const items = arr.reduce((acc: Array<CFReturnType | null>, curr) => {
    
    const formField = createForm(curr);
        return [...acc, formField];
      }, []);
    
    // filter out any null values due to early returns from hidden fields
      return items.filter(Boolean) as Array<CFReturnType>;
    };
    - `dataMap`
    
      - `dataMap` is an object with `inputMap` values as the key and related props as the value. All of these will contain a 'file' value. This is the name of the React component that will be dynamically imported, and some of them will contain a 'props' value. Props is an object that contains more details for that particular input type such as type, icon, masking function etc..
    
        ```javascript
    
        ```
    
    const dataMap: CFMappingType["dataMap"] = {
    
    // ...
    input: {
    file: "Input",
    props: { type: "text" },
    },
    phone: {
    file: "Input",
    props: {
    type: "tel",
    icon: "phone",
    mask: masks.tel,
    },
    },
    radio: {
    file: "RadioGroup",
    },
    
    // ...
    };
    2. Inside `createForm()` it will take the incoming field name and use it to index `inputMap`. At this point one of three things will happen:
       - It will find a match and return that input type
       - It will return a null value (these are currently hidden fields or unimportant fields)
       - It will return undefined\
         \
         If it returns a type we will continue, if it returns a null we return a null out of createForm(), if its undefined we then assume and assign this field with a type 'input'
    3. Now that we have the field type (the `inputType` value), we use this to index `dataMap` and return the file and or props from that
    
       - We take this file name and dynamically import that component (ie: 'Input', 'Heading', 'RadioGroup', 'Tabs', etc..)
    
       ```javascript
    4. We then now build our return object which gets added to the array in `parseFields()` and gets sent back to `<SingleStepForm />`
    
    ```javascript
    return {
      Component,
      data: getData(fields, title),
      file,
      formName: fields?.FormId?.value,
      id: uid(32),
      props: {
        columns: getColumnWidth(fields.ColumnWidth?.value),
        ...props,
      },
      validations: getValidations(file, fields, props?.type),
    };
    const getData: GetDataProps = ({ fields, title }) => ({
      customValidationErrorMsg:
        fields?.CustomValidationErrorMsg?.value || "field is required",
      items: parseItems(fields?.InputItems?.value),
      label: fields?.Label?.value || "",
      maxLength: parseInt(fields?.MaximumLength?.value) || 524288,
      minLength: parseInt(fields?.MinimumLength?.value) || 0,
      name: fields?.Name?.value,
      placeholder: fields?.PlaceholderText?.value,
      required: fields?.Required?.value || false,
      tabs: fields?.Tabs?.value.split("\n") || [],
      title,
      toolTipText: fields?.TooltipText?.value,
    });
    const getValidations: GetValidationProps = (file, fields, regex = "") => {
      const skipValidation = ["Heading", "Recaptcha", "Tabs"];
      let pattern;
    
      if (file && skipValidation.includes(file)) return null;
    
      // Validations will usually come through as a string value from sitecore
    
      // but can also come through as an array of objects, these have the type 'select'
    
      // We first need to parse through this array and grab the value of the selected validation pattern
      if (fields?.ValidationPattern?.type === "select") {
        pattern = getSelectedValue(fields.ValidationPattern.value);
      } else {
        pattern = fields?.ValidationPattern?.value;
      }
    
      return {
        shouldConfirm: fields?.AppearsOnFormConfirmation?.value,
        validationPattern:
          // 1. by regex in mapping props (phone, ssn)
    
          // 2. by named regex pattern coming from Sitecore
          regexMap[regex] || regexMap[pattern],
      };
    };
       return (
         <div className="relative">
           <InputText {...propData} />
           <div className="hidden lg:block absolute top-0 right-0 mt-4 lg:-mr-48">
             <Tooltip error={Boolean(errors[name])} message={toolTipText} />
           </div>
         </div>
       );
     };
     ```
    [[form stepper element], [...first section fields], [...second section fields] ...etc]
    const formModel: Array<ParsedFormModel> = JSON.parse(modelJson.value);
    
    const createdFields = useMemo(() => createFormInit(formModel, true), []);
    
    const createdFieldsWithoutTabs = [...createdFields.slice(1), []];
    
    const createdFieldsOnlyTabFields = [
      ...createdFields[0][0].data.tabs,
      "Confirmation",
    ];
    
    // ... return
    <FormComponent onSubmit={handleFormSubmit}>
      <FormStepper activeIndex={activeIndex} content={createdFieldsOnlyTabFields} />
      {createdFieldsWithoutTabs.map((innerArray, index) =>
        innerArray.map(
          ({ Component, data, id, props, validations, ...rest }: CFReturnType) => {
            return (
              <FieldWrapper
                className={index === activeIndex ? "block" : "hidden"}
                columns={props?.columns}
                key={id}
              >
                <Component
                  register={register({
                    pattern: regexPattern(validations),
                    required: isRequired(data),
                    validate: {
                      match: (value: string) =>
                        matchingEmails(
                          name.toLowerCase(),
                          value,
                          getValues(["email", "emailconf"])
                        ),
                    },
                  })}
                  {...{ data, errors, name, props, ...rest }}
                />
              </FieldWrapper>
            );
          }
        )
      )}
      {isLastStep && <ConfirmationStep data={confirmedValues} {...{ fields }} />}
      <ButtonWrapper />
    </FormComponent>;
    npm run storybook
    function greet(person: { name: string; age: number }) {  return "Hello " + person.name;}Try
    interface Person {  name: string;  age: number;} function greet(person: Person) {  return "Hello " + person.name;}Try