diff --git a/.changeset/bright-buckets-jam.md b/.changeset/bright-buckets-jam.md new file mode 100644 index 00000000000..ae59f1221ca --- /dev/null +++ b/.changeset/bright-buckets-jam.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +``: corrected micron set to allow for a range from 8px to 12px diff --git a/.changeset/perfect-llamas-matter.md b/.changeset/perfect-llamas-matter.md new file mode 100644 index 00000000000..5b01e570e75 --- /dev/null +++ b/.changeset/perfect-llamas-matter.md @@ -0,0 +1,5 @@ +--- +"@rhds/elements": patch +--- + +``: improved layout compatibility for plain variant avatars diff --git a/.changeset/spotty-buckets-stand.md b/.changeset/spotty-buckets-stand.md new file mode 100644 index 00000000000..28f7b6cf7c6 --- /dev/null +++ b/.changeset/spotty-buckets-stand.md @@ -0,0 +1,56 @@ +--- +"@rhds/elements": minor +--- + +✨ Added ``. + +The Primary navigation allows users to orient themselves and successfully move through web experiences. It is +persistent on every page to ensure a consistent user experience across our systems of website + +```html + + + AI dropdown content + + + + Hybrid Cloud dropdown content + + + + Products dropdown content + + + + Learn dropdown content + + + + Partners dropdown content + + + + Developers + + + + Docs + + + + Support + + + + Search dropdown content + + + + For you dropdown content + + + + My Red Hat dropdown content + + +``` \ No newline at end of file diff --git a/.pfe.config.json b/.pfe.config.json index 24dfafe5497..7ffed236120 100644 --- a/.pfe.config.json +++ b/.pfe.config.json @@ -5,7 +5,6 @@ "aliases": { "rh-cta": "Call to action", "rh-stat": "Statistic", - "rh-navigation": "Navigation (primary)", "rh-navigation-primary": "Navigation (primary)", "rh-navigation-secondary": "Navigation (secondary)", "rh-subnav": "Subnavigation" diff --git a/.stylelintrc.yml b/.stylelintrc.yml index 3927d8c48ec..2ca2dac8300 100644 --- a/.stylelintrc.yml +++ b/.stylelintrc.yml @@ -67,6 +67,7 @@ rules: - true - allowed: - --rh-icon-size + - --rh-navigation-active-item-color overrides: - files: diff --git a/docs/_data/relatedItems.yaml b/docs/_data/relatedItems.yaml index 751a204e57c..80c26f7bc16 100644 --- a/docs/_data/relatedItems.yaml +++ b/docs/_data/relatedItems.yaml @@ -66,7 +66,7 @@ rh-jump-links: - rh-pagination - rh-progress-steps - rh-tabs -rh-navigation: +rh-navigation-primary: - rh-navigation-secondary - skip-navigation - rh-subnav diff --git a/docs/_data/repoStatus.ts b/docs/_data/repoStatus.ts index a7be65b6727..0e35bf62226 100644 --- a/docs/_data/repoStatus.ts +++ b/docs/_data/repoStatus.ts @@ -312,11 +312,10 @@ export default [ tagName: 'rh-navigation-primary', name: 'Navigation (primary)', type: 'element', - description: `The Primary navigation is a container of menus and utilities, it allows visitors to orient themselves and move through a website. It is persistent on every page to ensure a consistent user experience across websites.`, overallStatus: 'planned', libraries: { figma: 'planned', - rhds: 'planned', + rhds: 'ready', shared: 'planned', docs: 'planned', }, diff --git a/docs/styles/demo/dev-server.css b/docs/styles/demo/dev-server.css index 9fa0270101e..e2028047ed2 100644 --- a/docs/styles/demo/dev-server.css +++ b/docs/styles/demo/dev-server.css @@ -32,7 +32,7 @@ display: contents; } - #main { + #rhds-dev-server-main { display: block; position: relative; max-height: initial; diff --git a/elements/rh-avatar/rh-avatar.css b/elements/rh-avatar/rh-avatar.css index 95aec0890d3..e2f81853daa 100644 --- a/elements/rh-avatar/rh-avatar.css +++ b/elements/rh-avatar/rh-avatar.css @@ -50,7 +50,7 @@ } :host([layout='block']) #container, -#container.mobile { +:host(:not([plain])) #container.mobile { text-align: center; place-items: center; gap: 0; diff --git a/elements/rh-icon/rh-icon.css b/elements/rh-icon/rh-icon.css index 44d42ef5493..36e16797c2d 100644 --- a/elements/rh-icon/rh-icon.css +++ b/elements/rh-icon/rh-icon.css @@ -20,5 +20,7 @@ svg { } .microns svg { - width: 12px; + min-width: 8px; + max-width: 12px; + width: var(--rh-icon-size, 12px); } diff --git a/elements/rh-navigation-primary/README.md b/elements/rh-navigation-primary/README.md new file mode 100644 index 00000000000..e6014a4bb72 --- /dev/null +++ b/elements/rh-navigation-primary/README.md @@ -0,0 +1,54 @@ +# Navigation Primary + +Primary navigation for top level site navigation. + +## Usage + + +```html + + + Item 1 + Item 1 Content + + + + Item 2 + Item 2 Content + + + + Item 3 + Item 3 Content + + + + Item 4 + Item 4 Content + + + + Item 5 + Item 5 Content + + + Link 1 + Link 2 + Link 3 + + + Item 6 + Item 6 Content + + + + Item 7 + Item 7 Content + + + + Item 8 + Item 8 Content + + +``` diff --git a/elements/rh-navigation-primary/context.ts b/elements/rh-navigation-primary/context.ts new file mode 100644 index 00000000000..ac985f91413 --- /dev/null +++ b/elements/rh-navigation-primary/context.ts @@ -0,0 +1,5 @@ +import { createContextWithRoot } from '@patternfly/pfe-core/functions/context.js'; + +export const context = createContextWithRoot( + Symbol('rh-navigation-primary-item-compact-context'), +); diff --git a/elements/rh-navigation-primary/demo/color-context.html b/elements/rh-navigation-primary/demo/color-context.html new file mode 100644 index 00000000000..650a06f0298 --- /dev/null +++ b/elements/rh-navigation-primary/demo/color-context.html @@ -0,0 +1,194 @@ + + + + + AI + AI Content + + + + Hybrid Cloud + Hybrid Cloud Content + + + + Products + Products Content + + + + Learn + Learn Content + + + + Partners + + + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + Console + + + Docs + + + Support + + + + + Search + Search content + + + + + For you + For you content + + + + + My Red Hat + +
+
+ + Mr. Roboto + AI UX Engineer, + Red Hat + +
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+ +
+
+ Log out +
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +

Mock Page content Link outside

+ +
+ + + + + + diff --git a/elements/rh-navigation-primary/demo/links-as-top-level.html b/elements/rh-navigation-primary/demo/links-as-top-level.html new file mode 100644 index 00000000000..fd31a67fdd6 --- /dev/null +++ b/elements/rh-navigation-primary/demo/links-as-top-level.html @@ -0,0 +1,186 @@ + + + AI + + + + Hybrid Cloud + + + + Products + + + + Learn + + + + Partners + + + + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + Console + + + Docs + + + Support + + + + + Search + Search content + + + + + For you + For you content + + + + + My Red Hat + +
+
+ + Mr. Roboto + AI UX Engineer, + Red Hat + +
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+ +
+
+ Log out +
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +

Mock Page content Link outside

+ + + + + + + diff --git a/elements/rh-navigation-primary/demo/not-defined.html b/elements/rh-navigation-primary/demo/not-defined.html new file mode 100644 index 00000000000..69bb902d409 --- /dev/null +++ b/elements/rh-navigation-primary/demo/not-defined.html @@ -0,0 +1,190 @@ + + + AI +
AI Content
+
+ + + Hybrid Cloud +
Hybrid Cloud Content
+
+ + + Products +
Products Content
+
+ + + Learn +
Learn Content
+
+ + + Partners +
Partners Content
+
+ + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + Console + + + Docs + + + Support + + + + + Search +
Search content
+
+ + + + For you +
For you Content
+
+ + + + My Red Hat + +
+
+ + Mr. Roboto + AI UX Engineer, + Red Hat + +
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+ +
+
+ Log out +
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +

Mock Page content Link outside

+ + + + + + diff --git a/elements/rh-navigation-primary/demo/rh-navigation-primary.html b/elements/rh-navigation-primary/demo/rh-navigation-primary.html new file mode 100644 index 00000000000..fed4326df55 --- /dev/null +++ b/elements/rh-navigation-primary/demo/rh-navigation-primary.html @@ -0,0 +1,435 @@ + + + AI +

AI Content

+
+
+ +
+
+ +
+
+ +
+
+
+ + + Hybrid Cloud +

Hybrid Cloud Content

+
+
+ +
+
+ +
+
+ +
+
+
+ + + Products +

Products Content

+
+
+ +
+
+ +
+
+ +
+
+
+ + + Learn + Learn Content + + + + Partners + Partners Content + + + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + Console + + + Docs + + + Support + + + + + Search +
+ + +
+
+ + + + For you +

Recommendations for you

+

As you browse redhat.com, we'll recommend resources you may like. For now, try these.

+ +
+ + + + My Red Hat + +
+
+
+ + +
+
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+
+ + +
+
+ Log out +
+
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +
+

Mock Page content Link outside

+
+ + + + + + diff --git a/elements/rh-navigation-primary/demo/right-to-left.html b/elements/rh-navigation-primary/demo/right-to-left.html new file mode 100644 index 00000000000..f0cf6df800b --- /dev/null +++ b/elements/rh-navigation-primary/demo/right-to-left.html @@ -0,0 +1,470 @@ +
+ + + AI +

בינה מלאכותית

+
+
+ +
+ + +
+
+ + + ענן היברידי +

Hybrid Cloud Content

+
+
+ +
+ + +
+
+ + + מוצרים +

Products Content

+
+
+ +
+ + +
+
+ + + ללמוד + תוכן + + + + שותפים + תוכן + + + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + לְנַחֵם + + + דוקומנטציה + + + תמיכה + + + + + חיפוש +
+ + + +
+
+ + + + מותאם אישית +

המלצות מותאמות אישית

+

As you browse redhat.com, we'll recommend resources you may like. For now, try these.

+ +
+ + + + My Red Hat + +
+
+
+ + +
+
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+
+ + +
+
+ Log out +
+
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +
+

תוכן עמוד מדומה קישור בתוכן העמוד

+
+
+ + + + + + diff --git a/elements/rh-navigation-primary/demo/translation.html b/elements/rh-navigation-primary/demo/translation.html new file mode 100644 index 00000000000..daab7535234 --- /dev/null +++ b/elements/rh-navigation-primary/demo/translation.html @@ -0,0 +1,188 @@ + + + KI + AI Content + + + + Hybride Cloud + Hybrid Cloud Content + + + + Produkte + Products Content + + + + Lernen + Learn Content + + + + Partner + + + + + + Red Hat Summit logo + + + + + + + + + + + + + + + + + + + Console + + + Dokumente + + + Unterstützung + + + + + Suchen + Inhalte durchsuchen + + + + + Für sie + Für Sie zufrieden + + + + + My Red Hat + +
+
+ + Mr. Roboto + AI UX Engineer, + Red Hat + +
Login: jdoe@redhat.com
+
Account number: 0123456
+
jdoe+alias@redhat.com
+
+ +
+
+ Log out +
+
+
+
    +
  • + Account details +

    Edit your contact info, password, location preferences, and errata notifications.

    +
  • +
  • + Community profile +

    Fill out your public profile and control what content you follow.

    +
  • +
  • + My Red Hat +

    Get a customized view of resources to help you use our products, solutions, and services.

    +
  • +
  • + Training & certification +

    Access your Red Hat Learning Subscription, courses, and exams.

    +
  • +
+
+
+ +
+
+ +
+
+ +

Mock Page content Link outside

+ + + + + + diff --git a/elements/rh-navigation-primary/docs/00-overview.md b/elements/rh-navigation-primary/docs/00-overview.md index 4949e7403a2..39589710ec4 100644 --- a/elements/rh-navigation-primary/docs/00-overview.md +++ b/elements/rh-navigation-primary/docs/00-overview.md @@ -1,4 +1,8 @@ -
- Example primary navigation element -
- + diff --git a/elements/rh-navigation-primary/docs/10-style.md b/elements/rh-navigation-primary/docs/10-style.md deleted file mode 100644 index 7997c19ed66..00000000000 --- a/elements/rh-navigation-primary/docs/10-style.md +++ /dev/null @@ -1,300 +0,0 @@ -## Style - -Elements in the primary navigation are high in contrast so they stand out to -visitors and meet accessibility guidelines. The primary navigation looks -similar in style to the [Footer](/elements/footer) for a -consistent user experience across websites. - - - Primary navigation - style - - - -### Anatomy - -The primary navigation is divided into **three zones** where content can be -placed. Each zone may include custom content and elements in certain zones -will collapse or become hidden completely as breakpoints get smaller. -**It is required to use all three zones.** - -- **Zone 1** - Website logo -- **Zone 2** - Menus -- **Zone 3** - Utilities - - -

Helpful tip

-

The website logo in Zone 1 should direct visitors to a home page when selected.

-
- - - Primary navigation - anatomy - - - -#### Website logo - -A branded logo corresponding to the website in which the primary navigation -is used. It will direct a visitor to the website home page when selected. - -#### Menus - -Text that triggers an expandable tray when selected. The content within is -specific to one website and does not appear d in the same format on other -websites. - -#### Utilities - -Actions or tools that display content within an expandable tray when -triggered or function as links. They can be unique to one website or global -across many websites. - - -### Expandable tray - -When menu text in Zone 2 is selected, an **expandable tray** will appear. -It is divided into three parts and is styled the same across all primary -navigation instances. - -1. **Tab** - visually informs a visitor of what menu they selected -1. **Tray** - the area to place content, links, etc. -1. **Overlay** - separates tray content from website content underneath - - - Primary navigation - expandable tray - - - -### Expandable tray tab - -When the expandable tray tab appears, text and icon colors are reversed. A tab -with a white background and red bar will also appear behind content. - - -

Helpful tip

-

Menu text displays a gray arrow on hover to indicate that an expandable tray will appear when triggered.

-
- - - Primary navigation - expandable tray tab - - - -### Layout - -The primary navigation spans the entire width of the browser window on all -breakpoints. - - - Primary navigation - layout (desktop) - - - - Primary navigation - layout (mobile) - - - -### Left-to-right languages - -When content is translated to other left-to-right languages, the primary -navigation maintains the same layout and text size. - - - Primary navigation - left-to-right languages - - - -### Right-to-left languages - -When content is translated to a right-to-left language like Hebrew, the text -size increases so visual subtleties of unique characters are easier to notice. - - - Primary navigation - right-to-left languages - - - -## Responsive design - -### Large breakpoints - -Both menus and utilities are visible in the primary navigation on large -breakpoints. - - - Primary navigation - large breakpoints - - - -### Medium breakpoints - -As breakpoints become smaller, menus will collapse into a utility and -accordion. **This includes full-width and fixed-width expandable -trays.** On tablet breakpoints, a menu utility replaces the horizontal -list of menus and maintains the same position for a smoother transition from -large to small breakpoints. - - - Primary navigation - medium breakpoints - - - -### Small breakpoints - -On small breakpoints, the menus and most of the utilities collapse into a menu -and accordion. - - - Primary navigation - small breakpoints - - - -## Best practices - -### Content overload - -Do not overload the primary navigation with too many menus and utilities. - - - Primary navigation - best practice 1 - - - -### Using icons alone - -Do not rely on icons alone to accurately represent content or actions, -ambiguity will not help visitors find what they need. - - - Primary navigation - best practice 2 - - - -### Change spacing - -Do not change the spacing between menus and utilities. - - - Primary navigation - best practice 3 - - - -### Hiding menus and utilities - -Do not hide menus and utilities on large breakpoints. - - - Primary navigation - best practice 4 - - - -### Mixing expandable trays - -Do not mix the full-width and fixed-width expandable trays within the same -menu group. - - - Primary navigation - best practice 5 - - - -## Spacing - -The primary navigation uses [spacers](/foundations/spacing) to define space values -between elements. - -### Extra large breakpoints - - - Primary navigation - Spacing for extra large breakpoints - - - -### Large breakpoints - - - Primary navigation - Spacing for large breakpoints - - - -### Medium breakpoints - - - Primary navigation - Spacing for medium breakpoints - - - -### Small breakpoints - - - Primary navigation - Spacing for small breakpoints - - - -### Fixed-width expandable tray - - - Primary navigation - Spacing for the fixed-width expandable tray - diff --git a/elements/rh-navigation-primary/docs/20-guidelines.md b/elements/rh-navigation-primary/docs/20-guidelines.md deleted file mode 100644 index f0abca132a1..00000000000 --- a/elements/rh-navigation-primary/docs/20-guidelines.md +++ /dev/null @@ -1,488 +0,0 @@ -## Usage - -### Primary navigation - -The primary navigation is used for wayfinding on all Red Hat web properties. -It should include the most important content a visitor needs or might be -looking for. - - - Primary navigation - usage - - - -### Full-width expandable tray - -Menus will trigger an expandable tray when selected and include content or -tasks that are specific to the website in which it is used. Use the expandable -tray to organize a large amount of content in two, three, or four columns. - - -

Warning

-

Do not use more than 4 columns in a full-width expandable tray, consider - using a component like [Tabs](/elements/tabs/) instead for - more organization options.

-
- - - - Primary navigation - full-width expandable tray (four columns) - - -If content is organized in less than four columns, containers will stretch to fill the columns. - - - Primary navigation - full-width expandable tray (three columns) - - - - Primary navigation - full-width expandable tray (two columns) - - - -### Fixed-width expandable tray - -Sometimes a small amount of content can be placed in a fixed-width expandable -tray and the size of this expandable tray depends on the amount of content. -Utilities are not able to leverage fixed-width expandable trays at this time. - - -

Warning

-

Do not use more than 2 columns to organize content in a fixed-width - expandable tray, consider using a full-width expandable tray instead.

-
- - - Primary navigation - fixed-width expandable tray - - - -### Menu slots - -There is no maximum number of menu slots, but be conscious of space when -adding menus especially when the text is translated to other languages. - - - Primary navigation - menu slots - - - -### Components in an expandable tray - -Use a component like [Tabs](/elements/tabs) to -organize a very large amount of content that would exceed four columns. If the -Tabs component is used, it will change to a nested [Accordion]({{ -'/elements/accordion' | url }}) on small breakpoints. - - -

Helpful tip

-

Red Hat data analysis has shown that displaying 3 tabs performs better than - displaying 5 tabs.

-
- - - Primary navigation - components in an expandable tray (desktop) - - - - Primary navigation - components in an expandable tray (mobile) - - - -### Utilities - -The primary navigation includes utilities which are slots for actions or tools -for global navigation (search for something, change the language, log in to -your account, etc.). They may trigger an expandable tray when selected, but -not all do. They are also customizable depending on specific audience needs or -goals. - - -

Helpful tip

-

The maximum number of utilities is 4 with the option of adding a fifth when - included as part of a personalized experience.

-
- - - Primary navigation - utilities - - - -### Utility ordering - -The order of some utilities can be customized or even removed, but not all. - -- The Account utility should always be visible on both large and small - breakpoints -- The Search and Account utilities should always be visible on large - breakpoints -- If a new utility is included as a part of a personalized experience, it - should be first -- If no personalized experiences are active, the Search utility should be - first and the Account utility should be last -- If a new utility requires a custom-designed icon, contact the [Brand - team](mailto:brand@redhat.com) - - - Primary navigation - utility ordering - - - -### Menus and utilities on small breakpoints - -When breakpoints become smaller, menus and most utilities will become hidden -to reduce visual crowding. The Account utility should always be visible on any -breakpoint. - - - Primary navigation - menus and utilities on small breakpoints - - - -## Website examples - -### redhat.com - - - Primary navigation - redhat.com example - - - -### Developer - - - Primary navigation - Developer example - - - -### Hybrid Cloud - - - Primary navigation - Hybrid Cloud example - - - -## Behavior - -### Scrolling - -The primary navigation is always sticky when scrolling on all devices and -breakpoints. - - -### Menus - -On hover, menu text will display a red bar and gray arrow indicating an -expandable tray will be triggered if selected. - - - Primary navigation - menus - - - -### Utility menus vs. links - -Some utilities leverage the expandable tray pattern or function as links. - - - Primary navigation - utility menus vs. links - - - -### Stacking - -When an [Announcement]({{ '/patterns/announcement' | -url }}) component is used, the primary navigation is positioned below it. - - - Primary navigation - stacking - - -### Scrolling with expandable tray - -If the height of the expandable tray is shorter than the viewport, content -will scroll underneath. - - - Primary navigation - scrolling with expandable tray - - -If the height of the expandable tray is taller than the viewport, the tray -will scroll instead. - - -

Warning

-

Be careful with featuring too much content because visitors may not see key - information if they have to scroll.

-
- - - Primary navigation - scrolling with expandable tray - - - -### Navigating between expandable trays - -Only one expandable tray can be visible at a time and there is no animation -when navigating from one tray to the next. - - - Primary navigation - navigating between expandable trays - - - -### Collapsing the expandable tray - -Clicking or tapping anywhere outside of the expandable tray should collapse -it. Pressing the **esc** key should collapse the expandable tray -as well. - - - Primary navigation - collapsing the expandable tray - - - -### Additional behaviors - -Keep in mind the following additional behaviors: - -- The expandable tray should not collapse or expand without user input by - mouse or keyboard -- Scrolling while the expandable tray is visible should not close the tray - - -## Interaction states - -### Default - - - Primary navigation - default interaction state - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StateElementStyling
DefaultMenu textRH Text Regular, 16pt / 24 (1.5) /#fff
DefaultUtility icon#fff
DefaultUtility textRH Text Regular, 12pt / 18 (1.5) /#fff
-
- -### Hover - - - Primary navigation - hover interaction state - - - - - - - - - - - - - - - - - - - - - - - -
StateElementStyling
HoverMenu and utility top bar#e00, 3px thickness
HoverArrow below menu text#6a6e73
-
- - -### Focus - - - Primary navigation - focus interaction state - - - -

Helpful tip

-

The focus state carries over styles from the hover state and also adds a - focus indicator.

-
- - - - - - - - - - - - - - - - - - - - - - -
StateElementStyling
FocusLogo, menu, and utility focus indicator#fff, dashed, 1px border width
FocusMenu and utility top bar#e00, 3px thickness
-
- - -### Active - - - Primary navigation - active interaction state - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StateElementStyling
ActiveTab top bar#e00, 3px thickness
ActiveTab background#fff
ActiveMenu textRH Text Regular, 16pt / 24 (1.5) /#151515
ActiveArrow below menu text#6a6e73
-
- - -## Accessibility - -### Focus order - -A logical focus order helps visitors **understand and operate** Red Hat web -properties. Elements need to receive focus in an order that preserves meaning, -therefore the focus order should make sense and not jump around randomly. - - - Primary navigation - focus order - diff --git a/elements/rh-navigation-primary/docs/30-code.md b/elements/rh-navigation-primary/docs/30-code.md new file mode 100644 index 00000000000..39589710ec4 --- /dev/null +++ b/elements/rh-navigation-primary/docs/30-code.md @@ -0,0 +1,8 @@ + diff --git a/elements/rh-navigation-primary/docs/overview.png b/elements/rh-navigation-primary/docs/overview.png deleted file mode 100644 index c7d57331944..00000000000 Binary files a/elements/rh-navigation-primary/docs/overview.png and /dev/null differ diff --git a/elements/rh-navigation-primary/docs/overview.svg b/elements/rh-navigation-primary/docs/overview.svg new file mode 100644 index 00000000000..97e4d430f7b --- /dev/null +++ b/elements/rh-navigation-primary/docs/overview.svg @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/rh-navigation-primary/docs/screenshot.png b/elements/rh-navigation-primary/docs/screenshot.png index 1d38a86c24b..b112d878bb3 100644 Binary files a/elements/rh-navigation-primary/docs/screenshot.png and b/elements/rh-navigation-primary/docs/screenshot.png differ diff --git a/elements/rh-navigation-primary/rh-navigation-primary-item-menu.css b/elements/rh-navigation-primary/rh-navigation-primary-item-menu.css new file mode 100644 index 00000000000..51887b7e984 --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-item-menu.css @@ -0,0 +1,17 @@ +:host { + display: block; +} + +#container { + color: var(--rh-color-text-primary); + background-color: var(--rh-color-surface); + padding: var(--rh-space-2xl, 32px) var(--rh-space-xl, 24px); + + &:not(.compact) { + @container (min-width: 1200px) { + margin: 0 auto; + max-width: 1440px; + padding: var(--rh-space-3xl, 48px) var(--rh-space-2xl, 32px); + } + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-item-menu.ts b/elements/rh-navigation-primary/rh-navigation-primary-item-menu.ts new file mode 100644 index 00000000000..efc9a619f77 --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-item-menu.ts @@ -0,0 +1,59 @@ +import { LitElement, html, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { state } from 'lit/decorators/state.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { consume } from '@lit/context'; +import { context } from './context.js'; + +import styles from './rh-navigation-primary-item-menu.css'; + +/** + * Navigation Menu + * @slot - Place element content here + */ +@customElement('rh-navigation-primary-item-menu') +export class RhNavigationPrimaryItemMenu extends LitElement { + static readonly styles = [styles]; + + #hydrated = false; + + @consume({ context, subscribe: true }) + @state() + private compact?: boolean; + + protected async firstUpdated(): Promise { + // ensure we update initially on client hydration + const _isHydrated = isServer && !this.hasUpdated; + if (!_isHydrated) { + this.#hydrated = true; + const root = this.getRootNode(); + if (root === document || !(root instanceof ShadowRoot)) { + return; + } + const nav = root.host.closest('rh-navigation-primary'); + await nav?.updateComplete; + if (!nav) { + this.compact = true; /* default to true if nav returns false */ + } else { + this.compact = nav.offsetWidth < 1200; + } + this.requestUpdate(); + } + } + + render() { + const compact = !this.#hydrated ? true : this.compact ?? true; + return html` +
+ +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'rh-navigation-primary-item-menu': RhNavigationPrimaryItemMenu; + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-item.css b/elements/rh-navigation-primary/rh-navigation-primary-item.css new file mode 100644 index 00000000000..07df416200d --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-item.css @@ -0,0 +1,394 @@ +*, +*:after, +*:before { + box-sizing: border-box; +} + +:host { + --_gradient: + linear-gradient(to right, + var(--rh-color-brand-red-light, #f56e6e), + var(--rh-color-purple-40, #876fd4), + var(--rh-color-purple-60, #3d2785)); + --_active-item-color: var(--_gradient); + + display: block; + block-size: 100%; + justify-items: center; + align-content: center; +} + +:host([hide-at]) { + display: none; +} + +@container (min-width: 320px) { + :host([hide-at='xs']) { + display: block; + } +} + +@container (min-width: 567px) { + :host([hide-at='sm']) { + display: block; + } +} + +@container (min-width: 768px) { + :host([hide-at='md']) { + display: block; + } +} + +@container (min-width: 992px) { + :host([hide-at='lg']) { + display: block; + } +} + +@container (min-width: 1200px) { + :host([hide-at='xl']) { + display: block; + } +} + +:host(:is(:focus, :focus-visible)) { + outline: none; + outline-style: none; +} + +#container { + --_background-color: + light-dark(var(--rh-color-surface-lighter, #f2f2f2), + var(--rh-color-surface-darker, #1f1f1f)); + --_box-shadow-color: light-dark(rgba(21, 21, 21, 0.2), rgba(0, 0, 0, 0.6)); + + display: block; + block-size: 100%; + inline-size: 100%; + justify-items: center; + align-content: center; + + & details { + inline-size: 100%; + block-size: 100%; + justify-items: center; + align-content: center; + + & summary { + --rh-icon-size: 18px /* non standard value */; + --rh-avatar-size: var(--rh-size-icon-02, 24px); + + display: flex; + list-style: none; + gap: var(--rh-space-md, 8px); + + &::marker, + &::-webkit-details-marker { + display: none; + } + + & ::slotted(rh-icon) { + /* need 24px total icons 18px + 3px margin * 2, avatar is 24px */ + margin-block: calc(var(--rh-space-sm, 6px) / 2); + } + + & rh-icon { + aspect-ratio: unset; + + &[icon='caret-down'] { + /* non standard, this wont take effect though as microns hard stops at 12px */ + --rh-icon-size: 10px; + + color: light-dark(var(--rh-color-gray-40, #a3a3a3), var(--rh-color-icon-subtle)); + transition: rotate 0.2s; + } + } + } + + & #details-content { + inline-size: 100%; + } + + &[open] { + &::details-content { + display: contents; + + & > * { + box-sizing: border-box; + } + } + + @container (min-width: 1200px) { + border-block-start-color: var(--rh-color-accent-brand); + } + + & summary { + & rh-icon[icon='caret-down'] { + rotate: 180deg; + } + } + } + } + + /* Variant States */ + &.hamburger { + &:not(.compact) { + @container (min-width: 1200px) { + border-block-end: 4px solid transparent; /* Non standard border width */ + + &:hover { + border-block-end-color: var(--rh-color-border-subtle); + } + + &:is(:active, :focus-within, :has(details[open])) { + border-image: var(--_active-item-color) 1; + } + } + } + } + + &.dropdown { + &.hamburger { + & details { + & summary { + list-style: none; + color: var(--rh-color-text-primary) !important; + text-decoration: none !important; + text-align: center; + text-wrap: nowrap; + place-items: center; + inline-size: 100%; + block-size: 100%; + padding: var(--rh-space-lg, 16px) var(--rh-space-xl, 24px); + cursor: pointer; + + &:focus-visible, + &:hover:focus-visible { + outline-offset: -2px; + outline: + var(--rh-border-width-md, 2px) + solid + var(--rh-color-interactive-primary-default); + border-radius: var(--rh-border-radius-default, 3px); + } + } + + &[open] { + border-inline-start: var(--rh-border-width-lg, 3px) solid var(--rh-color-accent-brand); + border-inline-end: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + + & summary { + & rh-icon[icon='caret-down'] { + rotate: 180deg; + } + } + } + } + + &:not(.compact) { + & details { + @container (min-width: 1200px) { + border-inline: unset; + } + + & summary { + @container (min-width: 1200px) { + block-size: calc(100% + 4px); + padding: 0 var(--rh-space-lg, 16px); + } + } + + & #details-content { + @container (min-width: 1200px) { + position: absolute; + z-index: -1; + background-color: var(--rh-color-surface); + box-shadow: 0 2px 4px 0 var(--_box-shadow-color); + inset-block-start: 100%; + inset-inline-start: 0; + inline-size: 100%; + } + } + } + } + } + + &.standalone { + & details { + /* stylelint-disable-next-line no-descending-specificity */ + & summary { + position: relative; + block-size: max-content; + inline-size: max-content; + align-items: center; + padding: var(--rh-space-md, 8px) var(--rh-space-lg, 16px); + border: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + border-radius: var(--rh-border-radius-pill, 64px); + font-size: var(--rh-font-size-body-text-sm, 0.875rem); + + &:before { + --_mask: conic-gradient(transparent 0 0); + + content: ''; + position: absolute; + inset: -1px; + border-radius: inherit; + padding: var(--rh-border-width-sm, 1px); + background: var(--_active-item-color); + mask: var(--_mask) content-box exclude, var(--_mask); + z-index: 1; + } + + &:active { + &:before { + padding: var(--rh-border-width-md, 2px); + } + } + + &:is(:hover, :focus-visible) { + background-color: var(--_background-color); + + &:before { + --_mask: conic-gradient(#000000 0 0); + } + } + + &:is(:focus-visible, :hover:focus-visible, :active) { + outline: + var(--rh-border-width-md, 2px) + solid + var(--rh-color-interactive-primary-default); + outline-offset: 3px; + border-radius: var(--rh-border-radius-pill, 64px); + } + + & #summary-text { + clip-path: inset(50%); + overflow: hidden; + position: absolute; + + @container (min-width: 1680px) { + clip-path: none; + overflow: none; + position: static; + } + } + } + + /* stylelint-disable-next-line no-descending-specificity */ + & #details-content { + position: absolute; + z-index: -1; + background-color: var(--rh-color-surface); + box-shadow: 0 2px 4px 0 var(--_box-shadow-color); + inset-block-start: 100%; + inset-inline-end: 0; + inline-size: 100%; + max-block-size: calc(100dvh - var(--_navbar-height)); + overflow-y: auto; + + @container (min-width: 1200px) { + inline-size: fit-content; + border-end-end-radius: calc(var(--rh-border-radius-default, 3px) * 2); + border-end-start-radius: calc(var(--rh-border-radius-default, 3px) * 2); + } + } + + &[open] { + /* stylelint-disable-next-line no-descending-specificity */ + summary { + background-color: var(--_background-color); + + &:before { + --_mask: conic-gradient(#000000 0 0); + + padding: var(--rh-border-width-md, 2px); + } + + & rh-icon[icon='caret-down'] { + rotate: 180deg; + } + } + } + } + } + } + + &.link { + display: inline-block; + block-size: fit-content; + border-block-start: none; + + & ::slotted(a) { + color: var(--rh-color-text-primary) !important; + } + + & ::slotted(:is(a:hover, a:focus-visible, :active)) { + text-underline-offset: 4px !important; + text-decoration-color: + light-dark(var(--rh-color-gray-50, #707070), + var(--rh-color-gray-40, #a3a3a3)) !important; + text-decoration-line: underline !important; + text-decoration-style: dashed !important; + text-decoration-skip-ink: auto !important; + color: var(--rh-color-interactive-primary-hover) !important; + } + + &.hamburger { + block-size: 100%; + + &:is(:hover, :focus-visible, :hover:focus-visible, :active) { + border-block-end-color: transparent; + } + + & ::slotted(a) { + display: flex; + align-items: center; + block-size: calc(100% + 4px); + font-size: var(--rh-font-size-body-text-md, 1rem); + padding: var(--rh-space-lg, 16px) var(--rh-space-xl, 24px); + } + + & ::slotted(a:is(:focus-visible, :hover:focus-visible, :active)) { + outline-offset: 0; + outline: var(--rh-border-width-md, 2px) solid var(--rh-color-interactive-primary-default); + border-radius: var(--rh-border-radius-default, 3px); + } + + @container (min-width: 1200px) { + & ::slotted(a) { + padding: 0 var(--rh-space-lg, 16px); + } + + &:is(:active, :focus-within, :has(details[open])) { + border-image: unset; + border-block-end-color: transparent; + } + } + } + + &.standalone { + padding-inline: + var(--_padding-inline-start, var(--rh-space-md, 8px)) + var(--_padding-inline-end, var(--rh-space-md, 8px)); + + & ::slotted(a) { + font-size: var(--rh-font-size-body-text-sm, 0.875rem) !important; + display: inline-flex; + block-size: auto; + align-items: center; + padding: var(--rh-space-md, 8px) !important; + } + + & ::slotted(a:is(:focus-visible, :hover:focus-visible, :active)) { + outline-offset: -2px; + outline: + var(--rh-border-width-md, 2px) + solid + var(--rh-color-interactive-primary-default); + border-radius: var(--rh-border-radius-default, 3px); + } + } + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-item.ts b/elements/rh-navigation-primary/rh-navigation-primary-item.ts new file mode 100644 index 00000000000..c7a3b071c45 --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-item.ts @@ -0,0 +1,133 @@ +import type { IconNameFor, IconSetName } from '@rhds/icons'; + +import { LitElement, html, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { state } from 'lit/decorators/state.js'; +import { query } from 'lit/decorators/query.js'; +import { property } from 'lit/decorators/property.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; + +import { consume } from '@lit/context'; +import { context } from './context.js'; + +import { themable } from '@rhds/elements/lib/themable.js'; + +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import '@rhds/elements/rh-icon/rh-icon.js'; + +import './rh-navigation-primary-item-menu.js'; + +import styles from './rh-navigation-primary-item.css'; + +@themable +@customElement('rh-navigation-primary-item') +export class RhNavigationPrimaryItem extends LitElement { + static readonly styles = [styles]; + + #highlight = false; + + // eslint-disable-next-line no-unused-private-class-members + #internals = InternalsController.of(this, { role: 'listitem' }); + + #hydrated = false; + + @query('details') + private _details!: HTMLDetailsElement; + + @property({ type: Boolean, reflect: true }) open = false; + + /* Summary text for dropdown variants only */ + @property() summary?: string; + + /* Variants 'link' | 'dropdown', link is the default if no variant is given */ + @property() variant?: 'link' | 'dropdown' = 'link'; + + /** + * Hides the element at various container query based breakpoints. + * Breakpoints available 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' + */ + @property({ reflect: true, attribute: 'hide-at' }) + hideAt?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' = undefined; + + /** Shorthand for the `icon` slot, the value is icon name */ + @property() icon?: IconNameFor; + + /** Icon set for the `icon` property - 'ui' by default */ + @property({ attribute: 'icon-set' }) iconSet?: IconSetName; + + @consume({ context, subscribe: true }) + @state() + private compact?: boolean; + + protected firstUpdated(): void { + // ensure we update initially on client hydration + const _isHydrated = isServer && !this.hasUpdated; + if (!_isHydrated) { + this.#hydrated = true; + const nav = this.closest('rh-navigation-primary'); + if (!nav) { + this.compact = true; /* default to true if nav returns false */ + } else { + this.compact = nav.offsetWidth < 1200; + } + /** + * SSR Adds the role, but then removes when ElementInternals is hydrated + * However, axe-dev tools then complains as it doesn't handle Internals correctly + * So.... lets readd it for brevity, then when axe decides to fix their stuff, + * we can remove at a later date. + */ + this.role = 'listitem'; + } + } + + render() { + const { variant = '' } = this; + const compact = this.compact ?? true; + const hamburger = (!this.getAttribute('slot')); + return html` +
${this.variant === 'dropdown' ? html` +
+ ${hamburger ? '' : html` + ${!this.icon ? '' : html` + `} + `} +
${this.summary}
+ +
+ + + +
` : html` + `} +
+ `; + } + + #detailsToggle() { + this.open = this._details.open; + this.dispatchEvent(new Event('toggle', { bubbles: true })); + } + + public hide() { + this._details.open = false; + } + + public show() { + this._details.open = true; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'rh-navigation-primary-item': RhNavigationPrimaryItem; + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-lightdom.css b/elements/rh-navigation-primary/rh-navigation-primary-lightdom.css new file mode 100644 index 00000000000..b6fa0b7ff81 --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-lightdom.css @@ -0,0 +1,58 @@ +rh-navigation-primary { + & rh-navigation-primary-item { + color: var(--rh-color-text-primary) !important; + + & a { + color: var(--rh-color-interactive-primary-default) !important; + text-decoration: none; + inline-size: 100%; + + @container navigation-primary (min-width: 1200px) { + padding: 0 var(--rh-space-lg); + } + + &:hover { + color: var(--rh-color-interactive-primary-hover) !important; + } + } + + &[slot='event']::part(container) { + padding: 0; + } + + &[slot='links'] { + display: flex; + align-items: center; + + &:after { + display: block; + content: ''; + border-inline-end: + var(--rh-border-width-sm, 1px) + solid + light-dark(var(--rh-color-gray-20, #e0e0e0), var(--rh-color-gray-60, #4d4d4d)); + height: 30px; + } + + &:nth-child(1 of rh-navigation-primary-item[slot='links']) { + --_padding-inline-start: 0; + + & a { + margin-inline-start: 0; + } + } + + &:nth-last-child(1 of rh-navigation-primary-item[slot='links']) { + --_padding-inline-end: 0; + + &:after { + border-inline-end-color: transparent; + } + + & a { + margin-inline-end: 0; + } + } + } + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-overlay.css b/elements/rh-navigation-primary/rh-navigation-primary-overlay.css new file mode 100644 index 00000000000..ec8bacaf987 --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-overlay.css @@ -0,0 +1,17 @@ +:host { + position: fixed; + background-color: rgb(var(--rh-color-gray-90-rgb, 31 31 31) / 0.8); + inset: 0; + inline-size: 100dvw; + block-size: 100dvh; + /* stylelint-disable-next-line rhds/no-unknown-token-name */ + z-index: var(--rh-overlay-z-index, 1); +} + +:host([open]) { + display: block; +} + +:host(:not([open])) { + display: none; +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary-overlay.ts b/elements/rh-navigation-primary/rh-navigation-primary-overlay.ts new file mode 100644 index 00000000000..6957c9356ac --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary-overlay.ts @@ -0,0 +1,22 @@ +import { LitElement } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { property } from 'lit/decorators/property.js'; + +import styles from './rh-navigation-primary-overlay.css'; + +/** + * Overlay + * @slot - Place element content here + */ +@customElement('rh-navigation-primary-overlay') +export class RhNavigationPrimaryOverlay extends LitElement { + static readonly styles = [styles]; + + @property({ type: Boolean, reflect: true }) open = false; +} + +declare global { + interface HTMLElementTagNameMap { + 'rh-navigation-primary-overlay': RhNavigationPrimaryOverlay; + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary.css b/elements/rh-navigation-primary/rh-navigation-primary.css new file mode 100644 index 00000000000..9271dfede1c --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary.css @@ -0,0 +1,374 @@ +*, +*:after, +*:before { + box-sizing: border-box; +} + +:host { + --_gradient: + linear-gradient(to right, + var(--rh-color-brand-red-light, #f56e6e), + var(--rh-color-purple-40, #876fd4), + var(--rh-color-purple-60, #3d2785)); + --_active-item-color: var(--_gradient); + --_navbar-height: 83px; + --_nav-container-z-index: -1; + + display: block; + container: navigation-primary / inline-size; + position: relative; + /* stylelint-disable-next-line rhds/no-unknown-token-name */ + z-index: var(--rh-navigation-primary-z-index, 102); + font-family: var(--rh-font-family-body-text, RedHatText, 'Red Hat Text', Helvetica, Arial, sans-serif) !important; +} + +:host([color-palette^='light']) { + color-scheme: only light; +} + +:host([color-palette^='dark']) { + color-scheme: only dark; +} + +#container { + --_box-shadow-color: light-dark(rgba(21, 21, 21, 0.2), rgba(0, 0, 0, 0.6)); + + display: block; + position: relative; + inline-size: 100%; + block-size: var(--_navbar-height); + z-index: 2; + + & #bar { + position: relative; + display: grid; + grid-template-areas: 'logo hamburger secondary-links'; + grid-template-columns: auto auto 1fr; + grid-template-rows: var(--_navbar-height); + background-color: + light-dark(var(--rh-color-surface-lightest, #ffffff), + var(--rh-color-surface-darkest, #151515)); + color: var(--rh-color-text-primary); + inline-size: 100%; + max-block-size: var(--_navbar-height); + box-shadow: 0 2px 4px 0 var(--_box-shadow-color); + padding-inline: var(--rh-space-lg, 16px); + + @container navigation-primary (min-width: 576px) { + padding-inline: var(--rh-space-2xl, 32px); + } + } + + & #logo { + grid-area: logo; + display: flex; + flex-direction: column; + margin-inline-end: var(--rh-space-lg, 16px); + border-block-end: none; + + @container navigation-primary (min-width: 576px) { + border-block-end-color: transparent; + } + + & ::slotted(a), + & a { + display: flex; + flex-direction: column; + justify-content: center; + block-size: -webkit-fill-available; + block-size: -moz-available; + block-size: fill-available; + block-size: 100%; + line-height: 0; + } + + & ::slotted(a:focus-visible), + & ::slotted(:hover:focus-visible), + & a:is(:focus-visible, :hover:focus-visible) { + outline-offset: -2px; + outline: + var(--rh-border-width-md, 2px) + solid + var(--rh-color-interactive-primary-default); + border-radius: var(--rh-border-radius-default, 3px); + } + } + + & #hamburger { + display: flex; + grid-area: hamburger; + block-size: 100%; + place-items: center; + align-content: center; + + @container navigation-primary (min-width: 1200px) { + grid-area: unset; + } + + & summary { + position: relative; + cursor: pointer; + display: flex; + gap: var(--rh-space-md, 8px); + block-size: max-content; + inline-size: max-content; + align-items: center; + padding: var(--rh-space-md, 8px) var(--rh-space-lg, 16px); + border: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + border-radius: var(--rh-border-radius-pill, 64px); + margin-inline-start: 0; + + &:before { + --_mask: conic-gradient(transparent 0 0); + + content: ''; + position: absolute; + inset: -1px; + border-radius: inherit; + padding: var(--rh-border-width-sm, 1px); + background: var(--_active-item-color); + mask: var(--_mask) content-box exclude, var(--_mask); + z-index: 1; + } + + &:active { + &:before { + padding: var(--rh-border-width-md, 2px); + } + } + + &:is(:hover, :focus-visible) { + background-color: + light-dark(var(--rh-color-surface-lighter, #f2f2f2), + var(--rh-color-surface-darker, #1f1f1f)); + + &:before { + --_mask: conic-gradient(#000000 0 0); + } + } + + &:is(:focus-visible, :hover:focus-visible, :active) { + outline: + var(--rh-border-width-md, 2px) + solid + var(--rh-color-interactive-primary-default); + outline-offset: 3px; + border-radius: var(--rh-border-radius-pill, 64px); + } + + &::marker, + &::-webkit-details-marker { + display: none; + } + + & rh-icon { + &[icon='caret-down'] { + /* non standard, this wont take effect though as microns hard stops at 12px */ + --rh-icon-size: 10px; + + color: light-dark(var(--rh-color-gray-40, #a3a3a3), var(--rh-color-icon-subtle)); + transition: rotate 0.2s; + } + + &[icon='menu-bars'] { + --rh-icon-size: 18px; /* non standard */ + + margin-block: var(--rh-space-xs, 4px); + color: var(--rh-color-icon-secondary); + } + } + + & #summary { + clip: rect(0 0 0 0); + clip-path: inset(50%); + block-size: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + inline-size: 1px; + font-size: var(--rh-font-size-body-text-sm, 0.875rem); + } + + @container (min-width: 992px) { + & #summary { + clip: unset; + clip-path: unset; + block-size: auto; + overflow: auto; + position: static; + white-space: normal; + inline-size: auto; + } + } + } + + & #details-content { + display: none; + } + + &[open] { + & summary { + &:before { + --_mask: conic-gradient(#000000 0 0); + + padding: var(--rh-border-width-md, 2px); + } + + & rh-icon[icon='caret-down'] { + rotate: 180deg; + } + } + } + } + + & #secondary { + grid-area: secondary-links; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + block-size: 100%; + gap: var(--rh-space-xs, 4px); + + @container navigation-primary (min-width: 768px) { + gap: var(--rh-space-md, 8px); + } + + #event { + display: none; + + @container navigation-primary (min-width: 768px) { + display: flex; + flex-direction: row; + } + } + + #links { + display: none; + + @container navigation-primary (min-width: 1440px) { + display: flex; + flex-direction: row; + } + } + + #dropdowns { + display: flex; + flex-direction: row; + gap: var(--rh-space-md, 8px); + + @container navigation-primary (min-width: 1200px) { + padding-inline-start: var(--rh-space-md, 8px); + } + } + } + + &:not(.compact) { + & #hamburger { + @container navigation-primary (min-width: 1200px) { + display: block; + + & > * { + box-sizing: border-box; + } + + /* stylelint-disable-next-line selector-pseudo-element-no-unknown */ + &::details-content { + display: contents; + + & > * { + box-sizing: border-box; + } + } + } + + /* stylelint-disable-next-line no-descending-specificity */ + & summary { + @container navigation-primary (min-width: 1200px) { + display: none; + } + } + + #details-content { + @container navigation-primary (min-width: 1200px) { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + block-size: 100%; + } + } + } + + & #hamburger[open] { + @container navigation-primary (min-width: 1200px) { + position: static; + display: flex; + block-size: 100%; + place-items: center; + align-content: center; + box-shadow: none; + background-color: transparent; + padding: 0; + + & ::slotted(rh-navigation-primary-item) { + border-block-start: none; + } + + & ::slotted(rh-navigation-primary-item:nth-last-child(1 of :not([slot]))) { + border-block-end: none; + } + } + } + } + + &.compact { + #hamburger { + &[open] { + & #details-content { + --_border-color: var(--rh-color-border-subtle); + + position: absolute; + inset-block-start: 100%; + inset-inline-start: 0; + z-index: var(--_nav-container-z-index); + display: flex; + flex-direction: column; + background-color: var(--rh-color-surface); + inline-size: 100%; + block-size: auto; + place-items: initial; + padding: var(--rh-space-lg, 16px); + box-shadow: var(--rh-box-shadow-sm, 0 2px 4px 0 rgba(21, 21, 21, 0.2)); + max-block-size: calc(100dvh - var(--_navbar-height)); + overflow-y: auto; + + & ::slotted(rh-navigation-primary-item) { + border-block-start: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + } + + & ::slotted(rh-navigation-primary-item:nth-last-child(1 of :not([slot]))) { + border-block-end: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + } + + @container navigation-primary (min-width: 320px) { + padding: var(--rh-space-2xl, 32px); + } + + @container navigation-primary (min-width: 1200px) { + & ::slotted(rh-navigation-primary-item) { + border-block-start: + var(--rh-border-width-sm, 1px) + solid + var(--rh-color-border-subtle); + } + + & ::slotted(rh-navigation-primary-item:nth-last-child(1 of :not([slot]))) { + border-block-end: var(--rh-border-width-sm, 1px) solid var(--rh-color-border-subtle); + } + } + } + } + } + } +} diff --git a/elements/rh-navigation-primary/rh-navigation-primary.ts b/elements/rh-navigation-primary/rh-navigation-primary.ts new file mode 100644 index 00000000000..a2d6523552f --- /dev/null +++ b/elements/rh-navigation-primary/rh-navigation-primary.ts @@ -0,0 +1,476 @@ +import { LitElement, html, isServer } from 'lit'; +import { customElement } from 'lit/decorators/custom-element.js'; +import { state } from 'lit/decorators/state.js'; +import { property } from 'lit/decorators/property.js'; +import { query } from 'lit/decorators/query.js'; +import { classMap } from 'lit/directives/class-map.js'; + +import { observes } from '@patternfly/pfe-core/decorators/observes.js'; +import { provide } from '@lit/context'; +import { context } from './context.js'; + +import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js'; +import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js'; + +import { colorPalettes, type ColorPalette } from '@rhds/elements/lib/color-palettes.js'; +import { themable } from '@rhds/elements/lib/themable.js'; + +import { RhNavigationPrimaryItem } from './rh-navigation-primary-item.js'; +import './rh-navigation-primary-overlay.js'; + +import '@rhds/elements/rh-icon/rh-icon.js'; + +import styles from './rh-navigation-primary.css'; + +export type NavigationPrimaryPalette = Extract; + +/** + * The Primary navigation is a container of menus and utilities, it allows + * visitors to orient themselves and move through a website. It is persistent on + * every page to ensure a consistent user experience across websites. + * + * @summary Primary navigation + * @slot - Place hamburger menu links and dropdowns + * @slot event - + * Use this slot for event promotion. Images such as SVGs and links are most often slotted here. + * Slot these items using the `` element. + * @slot links - + * Use this slot for quick links to other sites not directly associated with the page the + * navigation is on. Common use cases are developers docs and support. Slot these items using + * the `` element. + * @slot dropdowns - + * Use this slot for search, for you, and account dropdowns. Slot these items using the + * `` element. + * @cssprop [--rh-navigation-primary-z-index, 102] + * The initial z-index for the primary navigation element, default is 102. + */ +@customElement('rh-navigation-primary') +@colorPalettes +@themable +export class RhNavigationPrimary extends LitElement { + static readonly styles = [styles]; + + #internals = InternalsController.of(this, { role: 'navigation' }); + + #openPrimaryDropdowns = new Set(); + + #openSecondaryDropdowns = new Set(); + + #ro?: ResizeObserver; + + #hydrated = false; + + #slots = new SlotController(this, + 'logo', + 'summary', + 'links', + 'dropdowns', + null, + ); + + /** + * We should start in compact mode (mobile first) + * Later, after the element has fully hydrated, we can recompute + * whether to use the compact layout based on the viewport + * @internal + */ + @provide({ context }) + @state() compact = true; + + @state() + private _overlayOpen = false; + + @query('#hamburger') + private _hamburger!: HTMLDetailsElement; + + /** + * Sets the mobile toggle (hamburger) text, used for translations, defaults to 'Menu' + */ + @property({ attribute: 'mobile-toggle-label' }) mobileToggleLabel = 'Menu'; + + /** Sets color context for child components, overrides parent context */ + @property({ reflect: true, attribute: 'color-palette' }) colorPalette?: NavigationPrimaryPalette; + + /** + * Customize the default `aria-label` on the `