@gitlab/ui 53.3.0 → 54.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ # [54.0.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v53.3.0...v54.0.0) (2023-01-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlDisclosureDropdown:** Loosen action item type ([bb2b308](https://gitlab.com/gitlab-org/gitlab-ui/commit/bb2b3087c6d322aa0b67b7343b854a0796d27fde))
7
+ * **GlDisclosureDropdown:** Pass item to action ([8ac2cbf](https://gitlab.com/gitlab-org/gitlab-ui/commit/8ac2cbf80f81b4eff58e58a339b9fcad7bf40545))
8
+
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * **GlDisclosureDropdown:** Action items are now determined by the *lack* of an
13
+ href string. An item with an href string is rendered as a link item,
14
+ whether or not it has an action callback.
15
+
16
+ Before this change, if an item had *both* an href and an action, it
17
+ would render as a button. Now, it would render as an anchor.
18
+ * **GlDisclosureDropdown:** The `this` of the `GlDisclosureDropdown` action
19
+ callback is now `undefined` rather than the item it's attached to. It
20
+ now receives the item as a regular argument instead.
21
+
22
+ This makes it more similar to the `action` event that's emitted by the
23
+ component, such that the same function could be used in either way.
24
+
25
+ This also means that the same callback can be attached to all items,
26
+ instead of requiring a unique callback for each item.
27
+
1
28
  # [53.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v53.2.0...v53.3.0) (2023-01-24)
2
29
 
3
30
 
@@ -15,31 +15,37 @@ var script = {
15
15
  }
16
16
  },
17
17
  computed: {
18
- isActionItem() {
18
+ isLink() {
19
19
  var _this$item;
20
- return (_this$item = this.item) === null || _this$item === void 0 ? void 0 : _this$item.action;
20
+ return typeof ((_this$item = this.item) === null || _this$item === void 0 ? void 0 : _this$item.href) === 'string';
21
21
  },
22
22
  isCustomContent() {
23
23
  return Boolean(this.$scopedSlots.default);
24
24
  },
25
25
  itemComponent() {
26
- if (this.isActionItem) return {
27
- is: 'button',
26
+ const {
27
+ item
28
+ } = this;
29
+ if (this.isLink) return {
30
+ is: 'a',
28
31
  attrs: {
29
- ...this.item.extraAttrs,
30
- type: 'button'
32
+ href: item.href,
33
+ ...item.extraAttrs
31
34
  },
32
- listeners: {
33
- click: () => this.item.action()
34
- }
35
+ listeners: {}
35
36
  };
36
37
  return {
37
- is: 'a',
38
+ is: 'button',
38
39
  attrs: {
39
- href: this.item.href,
40
- ...this.item.extraAttrs
40
+ ...item.extraAttrs,
41
+ type: 'button'
41
42
  },
42
- listeners: {}
43
+ listeners: {
44
+ click: () => {
45
+ var _item$action;
46
+ return (_item$action = item.action) === null || _item$action === void 0 ? void 0 : _item$action.call(undefined, item);
47
+ }
48
+ }
43
49
  };
44
50
  }
45
51
  },
@@ -1,10 +1,6 @@
1
- const itemValidator = _ref => {
2
- let {
3
- text,
4
- href,
5
- action
6
- } = _ref;
7
- return Boolean((text === null || text === void 0 ? void 0 : text.length) && ((href === null || href === void 0 ? void 0 : href.length) || typeof action === 'function'));
1
+ const itemValidator = item => {
2
+ var _item$text;
3
+ return (item === null || item === void 0 ? void 0 : (_item$text = item.text) === null || _item$text === void 0 ? void 0 : _item$text.length) > 0 && !Array.isArray(item === null || item === void 0 ? void 0 : item.items);
8
4
  };
9
5
  const isItem = item => Boolean(item) && itemValidator(item);
10
6
  const isGroup = group => Boolean(group) && Array.isArray(group.items) && Boolean(group.items.length) && group.items.every(isItem);
package/dist/index.js CHANGED
@@ -86,7 +86,6 @@ export { default as GlAccordion } from './components/base/accordion/accordion';
86
86
  export { default as GlAccordionItem } from './components/base/accordion/accordion_item';
87
87
  export { default as GlCarousel } from './components/base/carousel/carousel';
88
88
  export { default as GlCarouselSlide } from './components/base/carousel/carousel_slide';
89
- export { default as RichTextEditor } from './components/editors/rich_text_editor/rich_text_editor';
90
89
  export { default as GlAnimatedNumber } from './components/utilities/animated_number/animated_number';
91
90
  export { default as GlFriendlyWrap } from './components/utilities/friendly_wrap/friendly_wrap';
92
91
  export { default as GlIntersperse } from './components/utilities/intersperse/intersperse';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "53.3.0",
3
+ "version": "54.0.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -44,8 +44,12 @@ After closing, `GlDisclosureDropdown` emits a `hidden` event.
44
44
 
45
45
  ### Setting disclosure dropdown items
46
46
 
47
- Use the `items` prop to provide actions/links to the disclosure dropdown. Each item can be
48
- either an item or a group. For `Item`s, either the `href` or the `action` may be null, but not both.
47
+ Use the `items` prop to provide actions/links to the disclosure dropdown. Each
48
+ item can be either an item or a group. For `Item`s, provide an `href` string to
49
+ make them render as links. Otherwise, they will be buttons. Provide an `action`
50
+ function to items to be called when they are pressed, or, listen for the
51
+ `action` event on the top-level component. Both will receive the given item as
52
+ an argument.
49
53
  A <!-- markdownlint-disable-next-line line-length -->
50
54
  [validation error](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6cbff4f908b429cc01f17a4cc2868e881db1aa31/src/components/base/new_dropdowns/disclosure/utils.js#L1)
51
55
  will be triggered if neither field is set.
@@ -56,7 +60,7 @@ Below are the expected shapes of these objects:
56
60
  type Item = {
57
61
  text: string
58
62
  href?: string,
59
- action?: function,
63
+ action?: (item: Item) => void,
60
64
  }
61
65
 
62
66
  type Group = {
@@ -75,15 +79,17 @@ template. If you want to render a custom template for items, use the
75
79
 
76
80
  ```html
77
81
  <gl-disclosure-dropdown :items="items">
78
- <template #list-item="{ item }">
79
- <a class="gl-hover-text-decoration-none gl-text-gray-900"
80
- tabindex="-1"
81
- :href="item.href"
82
- v-bind="item.extraAttrs">
83
- {{ item.text }}
84
- <gl-badge pill variant="info" v-if="item.count">{{ item.count }}</gl-badge>
85
- </a>
86
- </template>
82
+ <template #list-item="{ item }">
83
+ <a
84
+ class="gl-hover-text-decoration-none gl-text-gray-900"
85
+ tabindex="-1"
86
+ :href="item.href"
87
+ v-bind="item.extraAttrs"
88
+ >
89
+ {{ item.text }}
90
+ <gl-badge v-if="item.count" pill variant="info">{{ item.count }}</gl-badge>
91
+ </a>
92
+ </template>
87
93
  </gl-disclosure-dropdown>
88
94
  ```
89
95
 
@@ -18,8 +18,10 @@ describe('GlDisclosureDropdownItem', () => {
18
18
  describe('when default slot content provided', () => {
19
19
  const content = 'This is an item';
20
20
  const slots = { default: content };
21
+ const item = mockItems[1];
22
+
21
23
  beforeEach(() => {
22
- buildWrapper({}, slots);
24
+ buildWrapper({ item }, slots);
23
25
  });
24
26
 
25
27
  it('renders it', () => {
@@ -33,7 +35,7 @@ describe('GlDisclosureDropdownItem', () => {
33
35
  ${() => findItem().trigger('keydown', { code: SPACE })} | ${'SPACE'}
34
36
  `(`$event should emit 'action' event`, ({ trigger }) => {
35
37
  trigger();
36
- expect(wrapper.emitted('action')).toHaveLength(1);
38
+ expect(wrapper.emitted('action')).toEqual([[item]]);
37
39
  });
38
40
  });
39
41
 
@@ -55,10 +57,11 @@ describe('GlDisclosureDropdownItem', () => {
55
57
  });
56
58
 
57
59
  describe('when item has an `action`', () => {
58
- const action = jest.spyOn(mockItems[1], 'action');
60
+ const item = mockItems[1];
61
+ const action = jest.spyOn(item, 'action');
59
62
 
60
63
  beforeEach(() => {
61
- buildWrapper({ item: mockItems[1] });
64
+ buildWrapper({ item });
62
65
  action.mockClear();
63
66
  });
64
67
 
@@ -69,16 +72,23 @@ describe('GlDisclosureDropdownItem', () => {
69
72
  });
70
73
 
71
74
  it('should set correct attributes', () => {
72
- const attrs = { ...mockItems[1].extraAttrs };
75
+ const attrs = { ...item.extraAttrs };
73
76
  delete attrs.class;
74
- expect(findButton().classes()).toContain(mockItems[1].extraAttrs.class);
77
+ expect(findButton().classes()).toContain(item.extraAttrs.class);
75
78
  expect(findButton().attributes()).toMatchObject(attrs);
76
79
  });
77
80
 
78
81
  it('should call `action` on `click`', () => {
79
82
  findButton().trigger('click');
80
- expect(action).toHaveBeenCalled();
81
- expect(wrapper.emitted('action')).toHaveLength(1);
83
+ expect(action).toHaveBeenCalledTimes(1);
84
+
85
+ const actionThisArg = action.mock.contexts[0];
86
+ expect(actionThisArg).toBe(undefined);
87
+
88
+ const actionArgs = action.mock.calls[0];
89
+ expect(actionArgs).toEqual([item]);
90
+
91
+ expect(wrapper.emitted('action')).toEqual([[item]]);
82
92
  });
83
93
 
84
94
  it.each`
@@ -88,7 +98,7 @@ describe('GlDisclosureDropdownItem', () => {
88
98
  ${() => findItem().trigger('keydown', { code: SPACE })} | ${'SPACE'}
89
99
  `(`$event will execute action and emit 'action' event`, ({ trigger }) => {
90
100
  trigger();
91
- expect(wrapper.emitted('action')).toHaveLength(1);
101
+ expect(wrapper.emitted('action')).toEqual([[item]]);
92
102
  });
93
103
  });
94
104
  });
@@ -16,31 +16,34 @@ export default {
16
16
  },
17
17
  },
18
18
  computed: {
19
- isActionItem() {
20
- return this.item?.action;
19
+ isLink() {
20
+ return typeof this.item?.href === 'string';
21
21
  },
22
22
  isCustomContent() {
23
23
  return Boolean(this.$scopedSlots.default);
24
24
  },
25
25
  itemComponent() {
26
- if (this.isActionItem)
26
+ const { item } = this;
27
+
28
+ if (this.isLink)
27
29
  return {
28
- is: 'button',
30
+ is: 'a',
29
31
  attrs: {
30
- ...this.item.extraAttrs,
31
- type: 'button',
32
- },
33
- listeners: {
34
- click: () => this.item.action(),
32
+ href: item.href,
33
+ ...item.extraAttrs,
35
34
  },
35
+ listeners: {},
36
36
  };
37
+
37
38
  return {
38
- is: 'a',
39
+ is: 'button',
39
40
  attrs: {
40
- href: this.item.href,
41
- ...this.item.extraAttrs,
41
+ ...item.extraAttrs,
42
+ type: 'button',
43
+ },
44
+ listeners: {
45
+ click: () => item.action?.call(undefined, item),
42
46
  },
43
- listeners: {},
44
47
  };
45
48
  },
46
49
  },
@@ -1,5 +1,4 @@
1
- const itemValidator = ({ text, href, action }) =>
2
- Boolean(text?.length && (href?.length || typeof action === 'function'));
1
+ const itemValidator = (item) => item?.text?.length > 0 && !Array.isArray(item?.items);
3
2
 
4
3
  const isItem = (item) => Boolean(item) && itemValidator(item);
5
4
 
@@ -10,6 +10,7 @@ describe('isItem', () => {
10
10
  );
11
11
 
12
12
  it.each([
13
+ { text: 'Action' },
13
14
  { text: 'Action', href: 'gitlab.com' },
14
15
  {
15
16
  text: 'Action',
package/src/index.js CHANGED
@@ -98,7 +98,6 @@ export { default as GlAccordion } from './components/base/accordion/accordion.vu
98
98
  export { default as GlAccordionItem } from './components/base/accordion/accordion_item.vue';
99
99
  export { default as GlCarousel } from './components/base/carousel/carousel.vue';
100
100
  export { default as GlCarouselSlide } from './components/base/carousel/carousel_slide.vue';
101
- export { default as RichTextEditor } from './components/editors/rich_text_editor/rich_text_editor.vue';
102
101
 
103
102
  // Utilities
104
103
  export { default as GlAnimatedNumber } from './components/utilities/animated_number/animated_number.vue';
@@ -1,41 +0,0 @@
1
- import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
2
-
3
- var script = {};
4
-
5
- /* script */
6
- const __vue_script__ = script;
7
-
8
- /* template */
9
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_vm._v("This is a rich-text-editor")])};
10
- var __vue_staticRenderFns__ = [];
11
-
12
- /* style */
13
- const __vue_inject_styles__ = undefined;
14
- /* scoped */
15
- const __vue_scope_id__ = undefined;
16
- /* module identifier */
17
- const __vue_module_identifier__ = undefined;
18
- /* functional template */
19
- const __vue_is_functional_template__ = false;
20
- /* style inject */
21
-
22
- /* style inject SSR */
23
-
24
- /* style inject shadow dom */
25
-
26
-
27
-
28
- const __vue_component__ = __vue_normalize__(
29
- { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
30
- __vue_inject_styles__,
31
- __vue_script__,
32
- __vue_scope_id__,
33
- __vue_is_functional_template__,
34
- __vue_module_identifier__,
35
- false,
36
- undefined,
37
- undefined,
38
- undefined
39
- );
40
-
41
- export default __vue_component__;
@@ -1,38 +0,0 @@
1
- <!--
2
- Briefly describe the component's purpose here.
3
- This should correspond to the short description in Pajamas' website: https://design.gitlab.com/components/status/
4
- -->
5
-
6
- The Rich Text Editor is a UI component that provides a WYSIWYG editing
7
- experience for[GitLab Flavored Markdown](https://docs.gitlab.com/ee/user/markdown.md#gitlab-flavored-markdown-gfm)
8
- (GFM) in the GitLab application.
9
- It also serves as the foundation for implementing Markdown-focused editors that target other engines,
10
- like static site generators.
11
-
12
- <!-- Provide technical information on how to use the component, add code examples if relevant. -->
13
-
14
- <!--
15
- ## Dos and don'ts
16
-
17
- If relevant, describe how the component is expected to be used, and how it's not.
18
- -->
19
-
20
- <!--
21
- ## Browser compatibility
22
-
23
- If the component requires any polyfill or fallback on certain browsers, describe those requirements
24
- here.
25
- -->
26
-
27
- <!--
28
- ## Edge cases
29
-
30
- If the component has some known limitations, describe them here.
31
- -->
32
-
33
- <!--
34
- ## Deprecation warning
35
-
36
- If and when this component introduced API changes that would require deprecating old APIs, describe
37
- the changes here, and provide a migration paths to the new API.
38
- -->
@@ -1,9 +0,0 @@
1
- import { shallowMount } from '@vue/test-utils';
2
- import GlRichTextEditor from './rich_text_editor.vue';
3
-
4
- describe('GlRichTextEditor', () => {
5
- it('renders main components', () => {
6
- const wrapper = shallowMount(GlRichTextEditor);
7
- expect(wrapper).toBeInstanceOf(Object);
8
- });
9
- });
@@ -1,25 +0,0 @@
1
- import readme from './rich_text_editor.md';
2
- import GlRichTextEditor from './rich_text_editor.vue';
3
-
4
- export default {
5
- title: 'editor/Rich Text Editor',
6
- component: GlRichTextEditor,
7
- parameters: {
8
- docs: {
9
- description: {
10
- component: readme,
11
- },
12
- },
13
- },
14
- argTypes: {},
15
- };
16
-
17
- const Template = (_, { argTypes }) => ({
18
- components: { GlRichTextEditor },
19
- props: Object.keys(argTypes),
20
- template: `
21
- <gl-rich-text-editor />
22
- `,
23
- });
24
-
25
- export const Default = Template.bind({});
@@ -1,7 +0,0 @@
1
- <script>
2
- export default {};
3
- </script>
4
-
5
- <template>
6
- <div>This is a rich-text-editor</div>
7
- </template>