discourse/app/assets/stylesheets/common/base/search-menu.scss
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

391 lines
7.5 KiB
SCSS

@mixin user-item-flex {
display: flex;
flex-direction: column;
line-height: var(--line-height-medium);
color: var(--primary-high-or-secondary-low);
}
@mixin separator {
border-top: 1px solid var(--primary-low);
margin-top: 0.5em;
padding-top: 0.5em;
}
$search-pad-vertical: 0.25em;
$search-pad-horizontal: 0.5em;
.search-menu {
.menu-panel .panel-body-contents {
overflow-y: auto;
}
.search-input {
position: relative;
margin: 1px;
display: flex;
align-items: center;
border: 1px solid var(--primary-medium);
border-radius: var(--d-border-radius-large);
input#search-term {
border-width: 0;
border-radius: var(--d-border-radius-large);
margin-bottom: 0;
width: auto;
flex-grow: 1;
padding-right: 50px;
&:focus {
outline: none;
}
}
.btn.search-context {
margin: 2px;
margin-right: 0;
white-space: nowrap;
}
&:focus-within {
@include default-focus;
}
}
.heading {
padding: 5px 0 5px 5px;
.filter {
padding: 0 5px;
}
}
input[type="text"] {
margin-right: 0px;
}
.results {
display: flex;
flex-direction: column;
padding-top: $search-pad-vertical;
padding-bottom: $search-pad-vertical;
.list {
min-width: 100px;
.item {
.blurb {
// https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
word-break: break-word;
-webkit-hyphens: auto;
hyphens: auto;
margin-top: 0.25em;
}
}
}
.search-result-topic {
.first-line {
display: block;
line-height: var(--line-height-medium);
}
.second-line {
display: flex;
flex-wrap: wrap;
align-items: baseline;
.badge-wrapper {
margin-right: 0.5em;
}
.discourse-tags .discourse-tag {
margin-right: 0.35em;
}
}
}
.search-result-post {
.blurb {
font-size: var(--font-down-1);
}
}
.search-result-category {
.widget-link {
margin-bottom: 0;
}
}
.search-result-group .group-result,
.search-result-user .user-result {
display: flex;
align-items: center;
font-size: var(--font-down-1);
}
.search-result-group .group-result {
.d-icon,
.avatar-flair {
width: 20px;
height: 20px;
}
.avatar-flair,
.d-icon-users {
margin-right: 0.5em;
}
.avatar-flair {
border-radius: 50%;
&.avatar-flair-image {
background-repeat: no-repeat;
background-size: 100% 100%;
}
.d-icon {
margin-right: 0;
}
}
.group-names {
@include user-item-flex;
.name,
.slug {
@include ellipsis;
}
.name {
font-weight: 700;
}
}
}
.search-result-user .user-result {
.user-titles {
@include user-item-flex;
.username,
.name {
@include ellipsis;
}
.name {
font-weight: 700;
}
.username,
.name,
.custom-field {
color: var(--primary-high-or-secondary-low);
}
.custom-field {
font-size: var(--font-down-2);
}
}
}
.search-result-category,
.search-result-tag {
+ .search-result-user,
+ .search-result-group {
@include separator;
}
}
.search-result-user .user-result img.avatar,
.search-item-user img.avatar {
width: 20px;
height: 20px;
margin-right: 0.5em;
}
.label-suffix {
color: var(--primary-medium);
margin-right: 0.33em;
}
.search-item-tag {
color: var(--primary-high);
}
.extra-hint {
color: var(--primary-low-mid);
font-size: var(--font-down-1);
}
.search-item-slug {
.keyword {
margin-right: 0.33em;
}
.badge-wrapper {
font-size: var(--font-0);
margin-left: 2px;
}
}
.search-menu-initial-options {
+ .search-result-tag,
+ .search-result-category,
+ .search-result-user,
+ .search-result-group {
@include separator;
}
}
.search-menu-initial-options,
.search-result-tag,
.search-menu-assistant {
.search-item-prefix {
margin-right: 0.33em;
}
.badge-wrapper {
font-size: var(--font-0);
margin-right: 0.5em;
}
.search-link {
display: flex;
flex-wrap: wrap;
align-items: baseline;
@include ellipsis;
.d-icon {
margin-right: 0.33em;
vertical-align: middle;
}
.d-icon-tag {
font-size: var(--font-down-2);
}
.d-icon-search {
font-size: var(--font-down-1);
}
}
}
}
.browser-search-tip,
.search-random-quick-tip {
padding: $search-pad-vertical 1px;
font-size: var(--font-down-2);
color: var(--primary-medium);
.tip-label {
background-color: rgba(var(--tertiary-rgb), 0.1);
margin-right: 4px;
padding: 2px 4px;
display: inline-block;
&.tip-clickable {
cursor: pointer;
}
}
}
.search-menu-recent {
@include separator;
.heading {
display: flex;
justify-content: space-between;
h4 {
color: var(--primary-medium);
font-weight: normal;
margin-bottom: 0;
}
.clear-recent-searches {
cursor: pointer;
color: var(--primary-low-mid);
}
}
}
.browser-search-tip {
padding-top: 0.5em;
}
.searching {
position: absolute;
top: $search-pad-vertical + 0.2em;
right: $search-pad-horizontal;
min-height: 20px;
.spinner {
width: 12px;
height: 12px;
border-width: 2px;
margin: 0;
margin-top: 2px;
}
a.show-advanced-search,
a.clear-search {
padding: 0px 3px;
display: inline-block;
background-color: transparent;
.d-icon {
color: var(--primary-low-mid);
}
&:focus,
&:hover {
.d-icon {
color: var(--primary-high);
}
}
}
a.clear-search {
margin-right: 3px;
}
}
.no-results {
padding: $search-pad-vertical $search-pad-horizontal;
}
.search-link {
display: block;
padding: $search-pad-vertical $search-pad-horizontal;
// This is purposefully redundant
// the search widget can be used outside of the header
// and the focus/hover styles from the header in those cases wouldn't follow
&:focus,
&:hover {
background-color: var(--highlight-bg);
}
.topic {
display: block;
}
.topic-title {
color: var(--tertiary);
overflow-wrap: anywhere;
@supports not (overflow-wrap: anywhere) {
word-break: break-word;
}
margin-right: 0.25em;
}
}
.search-result-topic,
.search-result-post {
.search-link {
padding: 0.5em;
}
}
}
// these styles will become the default once the glimmer search menu
// is enabled for all users, and the old search menu is removed
// we can then drop any '!important' rules
.search-menu.glimmer-search-menu {
.search-item-tag .d-icon {
margin-right: 0 !important;
}
.search-item-user {
.username {
margin-right: 0.33rem;
}
img.avatar {
margin-right: 0 !important;
}
}
}