-
Notifications
You must be signed in to change notification settings - Fork 2
Scroll sections implementation #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
91da8df
Add ScrollSections components
stefashkaa e24a92d
Add disabled state for scroll section item
stefashkaa 03b7ad3
Add stories for scroll sections component
stefashkaa c579231
Fix issues
stefashkaa cb981c8
Fix boundary cases
stefashkaa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| <template> | ||
| <section :id="section" class="s-scroll-section-item"> | ||
| <span v-if="title" class="title">{{ title }}</span> | ||
| <slot v-if="this.$slots.title && !title" name="title"></slot> | ||
| <slot></slot> | ||
| </section> | ||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Vue, Component, Prop } from 'vue-property-decorator' | ||
|
|
||
| @Component | ||
| export default class SScrollSectionItem extends Vue { | ||
| /** | ||
| * Required section property of scroll section item. It should be unique. | ||
| * | ||
| * For, instance, if you want to go to the `your-url#article`, | ||
| * you should set `section="article"` | ||
| */ | ||
| @Prop({ default: '', type: String, required: true }) readonly section!: string | ||
| /** | ||
| * Title of scroll section item. Slot `title` can be used as well | ||
| */ | ||
| @Prop({ default: '', type: String }) readonly title!: string | ||
| /** | ||
| * Disabled state of scroll section item for menu. | ||
| * | ||
| * `false` by default | ||
| */ | ||
| @Prop({ default: false, type: Boolean }) readonly disabled!: boolean | ||
| } | ||
| </script> | ||
|
|
||
| <style lang="scss"> | ||
| .s-scroll-section-item { | ||
| padding: 10px 20px; | ||
| &:first-child { | ||
| margin-top: 10px; | ||
| } | ||
| &:last-child { | ||
| margin-bottom: 120%; | ||
| } | ||
| .title { | ||
| font-weight: bold; | ||
| display: block; | ||
| padding-bottom: 12px; | ||
| } | ||
| } | ||
| </style> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| <template> | ||
| <div class="s-scroll-sections flex"> | ||
| <nav class="s-scroll-menu" v-if="menuItems.length > 0"> | ||
| <ul :style="computedStyles"> | ||
| <li class="s-scroll-item" v-for="item in menuItems" :key="item.section"> | ||
| <a | ||
| :href="!(router || item.disabled) ? '#' + item.section : null" | ||
| :class="{ | ||
| 'active': item.section === activeSection, | ||
| 'disabled': item.disabled | ||
| }" | ||
| @click="goTo(item.section)" | ||
| > | ||
| {{ item.title }} | ||
| </a> | ||
| </li> | ||
| </ul> | ||
| </nav> | ||
| <div class="s-scroll-content"> | ||
| <slot></slot> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script lang="ts"> | ||
| import { Vue, Component, Prop } from 'vue-property-decorator' | ||
| import VueRouter from 'vue-router' | ||
|
|
||
| @Component | ||
| export default class SScrollSections extends Vue { | ||
| /** | ||
| * Text color of scroll menu in hex format. | ||
| * | ||
| * By default it's set to `"#75787B"` | ||
| */ | ||
| @Prop({ default: '#75787B', type: String }) readonly textColor!: string | ||
| /** | ||
| * Active text color of scroll menu in hex format. | ||
| * | ||
| * By default it's set to `"#D0021B"` | ||
| */ | ||
| @Prop({ default: '#D0021B', type: String }) readonly activeTextColor!: string | ||
| /** | ||
| * Active hover color of scroll menu in hex format. | ||
| * | ||
| * By default it's set to `"#D0021B"` | ||
| */ | ||
| @Prop({ default: '#D0021B', type: String }) readonly hoverColor!: string | ||
| /** | ||
| * `VueRouter` instance from `vue-router`. If it's null, then routing will be unavailable while scrolling. | ||
| */ | ||
| @Prop({ type: Object }) readonly router!: VueRouter | ||
|
|
||
| menuItems: Vue[] = [] | ||
| activeSection = '' | ||
|
|
||
| mounted (): void { | ||
| this.$nextTick(() => { | ||
| if (this.$children.length === 0) { | ||
| return | ||
| } | ||
| this.menuItems = this.$children | ||
| window.addEventListener('scroll', this.handleScroll) | ||
| this.handleInitialState() | ||
| }) | ||
| } | ||
|
|
||
| get computedStyles (): object { | ||
| const styles = {} as any | ||
| if (this.textColor) { | ||
| styles['--s-menu-color-text'] = this.textColor | ||
| } | ||
| if (this.activeTextColor) { | ||
| styles['--s-menu-color-text-active'] = this.activeTextColor | ||
| } | ||
| if (this.hoverColor) { | ||
| styles['--s-menu-color-hover'] = this.hoverColor | ||
| } | ||
| return styles | ||
| } | ||
|
|
||
| private handleInitialState (): void { | ||
| if (this.router && this.router.currentRoute.hash) { | ||
| this.menuItems.forEach((sectionComponent: any) => { | ||
| if (this.router.currentRoute.hash === `#${sectionComponent.section}`) { | ||
| (sectionComponent.$el as HTMLElement).scrollIntoView() | ||
| } | ||
| }) | ||
| } else if (window.scrollY <= (this.menuItems[0].$el as HTMLElement).offsetTop) { | ||
| this.activeSection = (this.menuItems[0] as any).section | ||
| this.menuItems[0].$el.classList.add('active') | ||
| if (!this.router) { | ||
| return | ||
| } | ||
| this.router.push({ hash: `#${this.activeSection}` }) | ||
| } | ||
| } | ||
|
|
||
| private handleScroll (): void { | ||
| const fromTop = Math.round(window.scrollY) | ||
| this.menuItems.forEach((sectionComponent, index) => { | ||
| const section = sectionComponent.$el as HTMLElement | ||
| const upperBound = section.offsetTop <= fromTop | ||
| const lowerBound = section.offsetTop + section.offsetHeight > fromTop | ||
| const underLowerBound = fromTop >= section.offsetTop + section.offsetHeight | ||
| if ((index === 0 && !upperBound) || | ||
| (upperBound && lowerBound) || | ||
| (index === this.menuItems.length - 1 && underLowerBound)) { | ||
| this.activeSection = (sectionComponent as any).section | ||
| section.classList.add('active') | ||
| if (this.router && this.router.currentRoute.hash !== `#${this.activeSection}`) { | ||
| this.router.push({ hash: `#${this.activeSection}` }) | ||
| } | ||
| } else { | ||
| section.classList.remove('active') | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| goTo (section: string): void { | ||
| if (!this.router || this.router.currentRoute.hash === `#${section}`) { | ||
| return | ||
| } | ||
| this.router.push({ hash: `#${section}` }) | ||
| const component = this.menuItems.filter((component: any) => component.section === section)[0] as any | ||
| (component.$el as HTMLElement).scrollIntoView({ behavior: 'smooth' }) | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <style lang="scss"> | ||
| @import "../../styles/variables.scss"; | ||
|
|
||
| .s-scroll-sections { | ||
| font-family: $font-family-default; | ||
| font-size: 14px; | ||
| } | ||
| .s-scroll-menu { | ||
| font-weight: 600; | ||
| flex: 1; | ||
| ul { | ||
| position: sticky; | ||
| top: 0; | ||
| list-style: none; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
| .s-scroll-item { | ||
| text-decoration: none; | ||
| a { | ||
| display: block; | ||
| padding: 8px 16px; | ||
| text-decoration: none; | ||
| cursor: pointer; | ||
| color: var(--s-menu-color-text); | ||
| &:hover { | ||
| color: var(--s-menu-color-hover); | ||
| } | ||
| &.active { | ||
| color: var(--s-menu-color-text-active); | ||
| border-left: 2px solid var(--s-menu-color-text-active); | ||
| } | ||
| &.disabled { | ||
| cursor: not-allowed; | ||
| pointer-events: none; | ||
| color: $color-neutral-inactive; | ||
| border-left: none; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| .s-scroll-content { | ||
| flex: 2; | ||
| } | ||
| </style> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| import SScrollSectionItem from './SScrollSectionItem.vue' | ||
| import SScrollSections from './SScrollSections.vue' | ||
|
|
||
| export { SScrollSectionItem, SScrollSections } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import StoryRouter from 'storybook-vue-router' | ||
| import { SScrollSectionItem, SScrollSections } from '../../components' | ||
|
|
||
| export default { | ||
| component: SScrollSectionItem, | ||
| title: 'Design System/Scroll Sections/Scroll Section Item', | ||
| decorators: [StoryRouter({}, { initialEntry: '/' })] | ||
| } | ||
|
|
||
| export const defaultUsage = () => ({ | ||
| components: { SScrollSections, SScrollSectionItem }, | ||
| template: `<s-scroll-sections :router="this.$router"> | ||
| <s-scroll-section-item | ||
| v-for="index in 11" | ||
| :key="index" | ||
| :section="'section' + index" | ||
| :title="'Section ' + index" | ||
| :disabled="index % 2 === 0" | ||
| > | ||
| <p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Officiis, blanditiis expedita? Earum eligendi pariatur quaerat quos expedita ab quibusdam ratione veniam in, mollitia fuga repudiandae?</p> | ||
| </s-scroll-section-item> | ||
| </s-scroll-sections>` | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import StoryRouter from 'storybook-vue-router' | ||
| import { SScrollSectionItem, SScrollSections } from '../../components' | ||
|
|
||
| export default { | ||
| component: SScrollSections, | ||
| title: 'Design System/Scroll Sections', | ||
| decorators: [StoryRouter({}, { initialEntry: '/' })] | ||
| } | ||
|
|
||
| export const defaultUsage = () => ({ | ||
| components: { SScrollSections, SScrollSectionItem }, | ||
| template: `<s-scroll-sections :router="this.$router"> | ||
| <s-scroll-section-item | ||
| v-for="index in 11" | ||
| :key="index" | ||
| :section="'section' + index" | ||
| :title="'Section ' + index" | ||
| > | ||
| <p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Officiis, blanditiis expedita? Earum eligendi pariatur quaerat quos expedita ab quibusdam ratione veniam in, mollitia fuga repudiandae?</p> | ||
| </s-scroll-section-item> | ||
| </s-scroll-sections>` | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the event be cleaned up after the object is destroyed?
I think we should add
removeEventListenerThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, thx. will add it in the next PR