18 Commits

Author SHA1 Message Date
Joffrey JAFFEUX
89ff7d51e6
UX: replaces custom more menu by d-menu (#29090)
One of the big advantages is a nicer menu on mobile.

This commit also fixes a bug where the close modal action was called for any destroyed d-menu trigger, even if this specific menu was not expanding, which means it was closing a different modal than its own modal, given we can only have one modal at a time.
2025-01-20 12:00:11 +01:00
David Taylor
72f4588d3b
DEV: Do not trigger floatkit 'hover' for touch events (#30793)
When tapping something on a touch-enabled device, `mouseMove` events are
still fired, so floatkit would still be triggered even if configured for
'hover' only. For links, this would be particularly strange, because the
tooltip would appear for a split-second, before the page navigation
occured.

To avoid this problem, we can use the more-modern 'pointerMove' event,
and check the `pointerType` to exclude 'touch'
2025-01-16 12:36:47 +00:00
Joffrey JAFFEUX
6740a340ca
DEV: unifies emoji picker (#28277)
The chat emoji picker is renamed emoji-picker, and the old emoji-picker is removed.

This commit doesn't attempt to fully rework a new emoji-picker but instead tries to migrate everything to one picker (the chat one) and add small changes.

Other notable changes:
- all the favorite emojis code has been mixed into one service which is able to store one state per context, favorites emojis will be stored for all topics, and for each chat channel. Meaning that if you always use a specific emoji in a channel, it will only show as favorite emoji in this channel.
- a lot of static code has been removed which should improve initial load perf of discourse. Initially this code was around to improve the performance of the emoji picker rendering.
- the emojis are now stored, once the full list has been loaded, if you close and reopen the picker it won't have to load them again.

List of components:

- `<EmojiPicker />` will render a button which will open a dropdown
- `<EmojiPickerContent />` represents the content of the dropdown alone, it's useful when you want to render a picker from an action which is not the default picker button
- `<EmojiPickerDetached />` just a simple wrapper over `<EmojiPickerContent />` to make it easier to use it with `this.menu.show(...)`

---------

Co-authored-by: Renato Atilio <renatoat@gmail.com>
2025-01-08 11:41:36 +01:00
Joffrey JAFFEUX
a5ba788a23
DEV: allows d-menu to refocus trigger (#30522) 2025-01-02 09:31:15 +01:00
Jarek Radosz
038e5deb2a
DEV: Clean up imports (#28060)
* `@ember/owner` instead of `@ember/application`
* `discourse-i18n` instead of `I18n`
* `{ service } from "@ember/service"` instead of `inject as service`
2024-07-25 15:09:06 +02:00
Jarek Radosz
a0aa8b0666
FIX: Properly close user-card after page transition (#27578)
This reverts commit b0e4b906ad770cd327024a638add7e40525789d3. (and re-lands 1ecfc397d36536795824d5b46d7687a5f76778f4)
2024-06-23 22:34:29 +02:00
Joffrey JAFFEUX
b0e4b906ad
Revert "FIX: Properly close user-card after page transition (#27423)" (#27456)
This reverts commit 1ecfc397d36536795824d5b46d7687a5f76778f4.
2024-06-12 21:11:44 +02:00
Jarek Radosz
1ecfc397d3
FIX: Properly close user-card after page transition (#27423) 2024-06-11 20:00:20 +02:00
Jarek Radosz
c972a31819
DEV: Fix typos and formatting (#27320) 2024-06-04 15:16:24 +02:00
Joffrey JAFFEUX
fe16633a0c
DEV: allows for multiple menus/tooltips (#26823)
menus and tooltips are now appended to their own portals. The service are the only responsible for managing the instances, prior to this commit, services could manage one instance, but the DMenu and DTooltip components could also take over which could cause unexpected states.

This change also allows nested menus/tooltips.

Other notable changes:

- few months ago core copied the CloseOnClickOutside modifier of float-kit without removing the float-kit one, this commit now only use the core one.
- the close function is now trully async
- the close function accepts an instance or an identifier as parameter
2024-05-07 23:48:44 +02:00
Joffrey JAFFEUX
bd7823cc70
FIX: prevents mobile DMenu modal to lock scroll (#26550)
The bug was due to the fact that the `<DModal />` is displayed inside a if block, when the condition was false to close the menu, the modal was just hidden without calling callbacks. The fix ensures we are correctly calling `modal.close()` before in this case.
2024-04-08 09:47:50 +02:00
Jarek Radosz
5d90332cfc
DEV: Use the "new" service import (#26059) 2024-03-06 18:05:11 +01:00
David Taylor
48193767bf DEV: Sort imports
Automatically generated by `eslint --fix` to satisfy the updated configuration
2023-10-10 21:46:54 +01:00
Joffrey JAFFEUX
2a10ea0e3f
DEV: FloatKit (#23650)
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":

- menu
- tooltip
- toast


## Tooltips
### Component

Simple cases can be express with an API similar to DButton:

```hbs
<DTooltip 
  @Label={{i18n "foo.bar"}}
  @ICON="check"
  @content="Something"
/>
```

More complex cases can use blocks:

```hbs
<DTooltip>
  <:trigger>
   {{d-icon "check"}}
   <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
   Something
  </:content>
</DTooltip>
```

### Service

You can manually show a tooltip using the `tooltip` service:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  options
)

// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();

// you can also just close any open tooltip through the service
this.tooltip.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const tooltipInstance = this.tooltip.register(
  document.querySelector(".my-span"),
  options
)

// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  { 
    component: MyComponent,
    data: { foo: 1 }
  }
)
```

## Menus

Menus are very similar to tooltips and provide the same kind of APIs:

### Component

```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
  <ul>
    <li>Foo</li>
    <li>Bat</li>
    <li>Baz</li>
  </ul>
</DMenu>
```

They also support blocks:

```hbs
<DMenu>
  <:trigger>
    {{d-icon "plus"}}
    <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
    <ul>
      <li>Foo</li>
      <li>Bat</li>
      <li>Baz</li>
    </ul>
  </:content>
</DMenu>
```

### Service

You can manually show a menu using the `menu` service:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  options
)

// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();

// you can also just close any open tooltip through the service
this.menu.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const menuInstance = this.menu.register(
   document.querySelector(".my-span"),
   options
)

// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  { 
    component: MyComponent,
    data: { foo: 1 }
  }
)
```


## Toasts

Interacting with toasts is made only through the `toasts` service.

A default component is provided (DDefaultToast) and can be used through dedicated service methods:

- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });

```javascript
this.toasts.success({
  data: {
    title: "Foo",
    message: "Bar",
    actions: [
      {
        label: "Ok",
        class: "btn-primary",
        action: (componentArgs) => {
          // eslint-disable-next-line no-alert
          alert("Closing toast:" + componentArgs.data.title);
          componentArgs.close();
        },
      }
    ]
  },
});
```

You can also provide your own component:

```javascript
this.toasts.show(MyComponent, {
  autoClose: false,
  class: "foo",
  data: { baz: 1 },
})
```

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-26 13:39:52 +02:00
Joffrey JAFFEUX
85fddf58bc
Revert "DEV: FloatKit (#23541)" (#23549)
This reverts commits

0623ac684a65f95ce4f5438ff47944b1dc2119f4
408e71e437ef8abf98f93e3449898bf906766b0e
a32fa3b9470dde0543fcee809f2abd1f219f7336

User tips were running into some issues.
2023-09-12 13:55:12 -04:00
Joffrey JAFFEUX
0623ac684a
DEV: FloatKit (#23541)
Second iteration of https://github.com/discourse/discourse/pull/23312 with a fix for embroider not resolving an export file using .gjs extension.

---

This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":

- menu
- tooltip
- toast


## Tooltips
### Component

Simple cases can be express with an API similar to DButton:

```hbs
<DTooltip 
  @label={{i18n "foo.bar"}}
  @icon="check"
  @content="Something"
/>
```

More complex cases can use blocks:

```hbs
<DTooltip>
  <:trigger>
   {{d-icon "check"}}
   <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
   Something
  </:content>
</DTooltip>
```

### Service

You can manually show a tooltip using the `tooltip` service:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  options
)

// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();

// you can also just close any open tooltip through the service
this.tooltip.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const tooltipInstance = this.tooltip.register(
  document.querySelector(".my-span"),
  options
)

// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  { 
    component: MyComponent,
    data: { foo: 1 }
  }
)
```

## Menus

Menus are very similar to tooltips and provide the same kind of APIs:

### Component

```hbs
<DMenu @icon="plus" @label={{i18n "foo.bar"}}>
  <ul>
    <li>Foo</li>
    <li>Bat</li>
    <li>Baz</li>
  </ul>
</DMenu>
```

They also support blocks:

```hbs
<DMenu>
  <:trigger>
    {{d-icon "plus"}}
    <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
    <ul>
      <li>Foo</li>
      <li>Bat</li>
      <li>Baz</li>
    </ul>
  </:content>
</DMenu>
```

### Service

You can manually show a menu using the `menu` service:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  options
)

// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();

// you can also just close any open tooltip through the service
this.menu.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const menuInstance = this.menu.register(
   document.querySelector(".my-span"),
   options
)

// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  { 
    component: MyComponent,
    data: { foo: 1 }
  }
)
```


## Toasts

Interacting with toasts is made only through the `toasts` service.

A default component is provided (DDefaultToast) and can be used through dedicated service methods:

- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });

```javascript
this.toasts.success({
  data: {
    title: "Foo",
    message: "Bar",
    actions: [
      {
        label: "Ok",
        class: "btn-primary",
        action: (componentArgs) => {
          // eslint-disable-next-line no-alert
          alert("Closing toast:" + componentArgs.data.title);
          componentArgs.close();
        },
      }
    ]
  },
});
```

You can also provide your own component:

```javascript
this.toasts.show(MyComponent, {
  autoClose: false,
  class: "foo",
  data: { baz: 1 },
})
```

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-12 15:50:26 +02:00
Joffrey JAFFEUX
b8cc1072cc
Revert "DEV: FloatKit (#23312)" (#23540)
This reverts commit abcdd8d367e8db430119f847f029e9cc94e62cb6.
2023-09-12 15:37:16 +02:00
Joffrey JAFFEUX
abcdd8d367
DEV: FloatKit (#23312)
This PR introduces three new UI elements to Discourse codebase through an addon called "FloatKit":

- menu
- tooltip
- toast

Simple cases can be express with an API similar to DButton:

```hbs
<DTooltip
  @label={{i18n "foo.bar"}}
  @icon="check"
  @content="Something"
/>
```

More complex cases can use blocks:

```hbs
<DTooltip>
  <:trigger>
   {{d-icon "check"}}
   <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
   Something
  </:content>
</DTooltip>
```

You can manually show a tooltip using the `tooltip` service:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  options
)

// and later manually close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();

// you can also just close any open tooltip through the service
this.tooltip.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const tooltipInstance = this.tooltip.register(
  document.querySelector(".my-span"),
  options
)

// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const tooltipInstance = await this.tooltip.show(
  document.querySelector(".my-span"),
  {
    component: MyComponent,
    data: { foo: 1 }
  }
)
```

Menus are very similar to tooltips and provide the same kind of APIs:

```hbs
<DMenu @icon="plus" @label={{i18n "foo.bar"}}>
  <ul>
    <li>Foo</li>
    <li>Bat</li>
    <li>Baz</li>
  </ul>
</DMenu>
```

They also support blocks:

```hbs
<DMenu>
  <:trigger>
    {{d-icon "plus"}}
    <span>{{i18n "foo.bar"}}</span>
  </:trigger>
  <:content>
    <ul>
      <li>Foo</li>
      <li>Bat</li>
      <li>Baz</li>
    </ul>
  </:content>
</DMenu>
```

You can manually show a menu using the `menu` service:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  options
)

// and later manually close or destroy it
menuInstance.close();
menuInstance.destroy();

// you can also just close any open tooltip through the service
this.menu.close();
```

The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:

```javascript
const menuInstance = this.menu.register(
   document.querySelector(".my-span"),
   options
)

// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```

Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:

```javascript
const menuInstance = await this.menu.show(
  document.querySelector(".my-span"),
  {
    component: MyComponent,
    data: { foo: 1 }
  }
)
```

Interacting with toasts is made only through the `toasts` service.

A default component is provided (DDefaultToast) and can be used through dedicated service methods:

- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });

```javascript
this.toasts.success({
  data: {
    title: "Foo",
    message: "Bar",
    actions: [
      {
        label: "Ok",
        class: "btn-primary",
        action: (componentArgs) => {
          // eslint-disable-next-line no-alert
          alert("Closing toast:" + componentArgs.data.title);
          componentArgs.close();
        },
      }
    ]
  },
});
```

You can also provide your own component:

```javascript
this.toasts.show(MyComponent, {
  autoClose: false,
  class: "foo",
  data: { baz: 1 },
})
```

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
2023-09-12 15:06:51 +02:00