Как установить одинаковую динамическую ширину дочернего компонента для всех экземпляров?Веб-компоненты - PullRequest
0 голосов
/ 05 декабря 2018

Предположим, у меня есть небольшой компонент в веб-приложении, представляющий элемент управления расширения, т. Е. Текст заголовка, значок для развертывания / свертывания и некоторый контент.

Очень простая реализация React (псевдокод) можетвыглядит следующим образом:

const Expander = ({children, title, onExpandToggle, isExpanded}) => (
  <div>
    <div><span>{title}</span><img src={...} onClick={onExpandToggle} /></div>
    {isExpanded && children}
  </div>
);

Эта реализация показывает значок после заголовка, поэтому положение значка определяется длиной заголовка.

Это может выглядеть примерно так:
Example

Теперь предположим, что есть несколько подобных друг другу.Это становится грязным:
Example messy

Чтобы сделать это более чистым, все значки должны иметь одинаковый отступ слева.Заполнение должно быть не фиксированным, а динамическим, так что самый длинный заголовок определяет положение всех значков:
Example clean

Предполагая, что я хочу оставить экспандер в своем собственномкомпонент, есть ли способ CSS для достижения моей цели?

Пока я ничего не пробовал, так как у меня нет отправной точки.В WPF я бы использовал что-то вроде SharedSizeGroup, но этого не существует для CSS.

Ответы [ 8 ]

0 голосов
/ 13 декабря 2018

Возможно.

1.Flex-box + Absolution:

Если вы не против использовать контейнер с абсолютным позиционированием, вы можете сделать это следующим образом

.container {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  width: auto;
}

.item {
  margin: 5px 0;
  padding: 5px;
  border: 1px solid black;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header img {
  width: 8px;
  height: 8px;
  margin-left: 20px;
  cursor: pointer;
}
<div class="container">
  <div class="item">
    <div class="header">
      <span>Synonyms</span>
      <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
    </div>
  </div>
  <div class="item">
    <div class="header">
      <span>Concept</span>
      <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
    </div>
  </div>
  <div class="item">
    <div class="header">
      <span>Term</span>
      <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
    </div>
  </div>
</div>

Основная проблема заключается в том, что вы не знаете высоту контейнера, что может повлиять на другое содержимое.

2.Inline Flex-box:

, если для вас критически важно положение контейнера, вы можете использовать inline-flex

.wrapper {
  margin: 20px 10px;
}

.container {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  width: auto;
}

.item {
  margin: 5px 0;
  padding: 5px;
  border: 1px solid black;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header img {
  width: 8px;
  height: 8px;
  margin-left: 20px;
  cursor: pointer;
}
Some content above the container...
<div class="wrapper">
  <div class="container">
    <div class="item">
      <div class="header">
        <span>Synonyms</span>
        <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
      </div>
    </div>
    <div class="item">
      <div class="header">
        <span>Concept</span>
        <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
      </div>
    </div>
    <div class="item">
      <div class="header">
        <span>Term</span>
        <img src="https://image.flaticon.com/icons/png/512/61/61155.png" />
      </div>
    </div>
  </div>
</div>
Some content below the container...
0 голосов
/ 10 декабря 2018
const Expander = (children, title, onExpandToggle, isExpanded) => (
   <div>
     {title}
     <img src={...} style={{marginLeft : '5px',float : 'right'}} onClick={onExpandToggle}/>
   </div>
   <div style={{width : '0px',overflowX : 'visible'}}>
     {isExpanded && <div>
       {children}
     </div>}
   </div>
);

И положить в:

<div style={{display : 'inline-block'}}>
   <Expander />
   <Expander />
</div>
0 голосов
/ 12 декабря 2018

В CSS разные элементы страницы не могут «знать» друг друга.Самая близкая вещь, которую вы можете получить, это использовать гибкий экран и / или сетку.Таким образом, прямой родитель может контролировать то, как будут отображаться его дочерние элементы, что-то вроде React.

Основное различие между flex и grid состоит в том, что flex это 1 ось, а grid 2 ось.

Рабочий пример с использованием grid и flex:

<div class="container">
    <div class="summary"><span class="text">Synonyms</span> <span class="icon">+</span></div>
    <div class="details">
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet officiis sequi perspiciatis sapiente iure minus laudantium! Quidem iure consectetur quod odit iusto, labore sunt quae, enim nesciunt officiis, quia ad.</p>
    </div>
    <div class="summary"><span class="text">Concept</span> <span class="icon">+</span></div>
    <div class="details">
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet officiis sequi perspiciatis sapiente iure minus laudantium! Quidem iure consectetur quod odit iusto, labore sunt quae, enim nesciunt officiis, quia ad.</p>
    </div>
    <div class="summary"><span class="text">Terms</span> <span class="icon">+</span></div>
    <div class="details">
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet officiis sequi perspiciatis sapiente iure minus laudantium! Quidem iure consectetur quod odit iusto, labore sunt quae, enim nesciunt officiis, quia ad.</p>
    </div>
</div>
<style>
    .container {
        display: grid;
        /* summary's width needs to be relative to the longest text
           and details needs to span the whole grid */
        grid-template-columns: auto auto; 
    }
    .summary {
        grid-column: 1 / 2; /* start at the beginning of 1st column and end at the beginning of the 2nd column */
        display: flex; /* create horizontal layout */
        justify-content: space-between; /* place the available space between the children */
    }
    .details {
        grid-column: 1 / 3; /* start at the beginning of 1st column and end at the end of the 2nd column */
    }
</style>

Как видите, он очень удобочитаемый и требует очень небольшой разметки.

С flex и grid вам нужнодумать на 2 уровнях parent > child.Это означает, что вам нужно соответствующим образом изменить компонент React:

const Expander = ({children, title, onExpandToggle, isExpanded}) => (
    <>
        <div class="summary" onClick={onExpandToggle}><span class="title">{title}</span> <span class="icon">+</span></div>
        {isExpanded && (
            <div class="details">
                {children}
            </div>
        )}
    </>
);

Здесь я удалил оболочку div, чтобы использовать Fragment, чтобы .summary и .details были прямыми потомками.container.Обратите внимание, что я также переместил onClick на весь заголовок по причинам UX.

CSS Grid очень полезен и гибок, его стоит изучить .Браузер поддерживает недавно, но довольно хорошо, просто убедитесь, что вы используете авторефиксер.

Вы также можете использовать трюки, используя display: inline-block;, размер которого основан на его дочерних элементах, но будет включать обходные пути, когда одиниз детей нужно переполнить (как в вашем случае).Поэтому я не рекомендую это.Я бы также советовал не использовать Javascript.

Не стесняйтесь сказать мне, что вы думаете об этом ответе.

0 голосов
/ 10 декабря 2018

Самый простой пример, который я могу придумать, основанный на вашем фрагменте кода, будет выглядеть примерно так:

 const containerStyle = {
   width: `[whatever width you need for the container]`,
   display: `flex`,
   flexDirection: `column`    
 }

 const itemStyle = { 
  width: `100%`, 
  display: `flex`, 
  flexDirection: `row`, 
  justifyContent: `space-between` 
 }

const Expander = ({children, title, onExpandToggle, isExpanded}) => (
  <div style={containerStyle}>
    <div style={itemStyle}>
      <span>{title}</span>
      <img src={...} onClick={onExpandToggle} />
    </div>
    {isExpanded && children}
  </div>
);
0 голосов
/ 09 декабря 2018

Лично я бы сделал это с классическим <table>.Это легко, семантически правильно, и это будет сделано за 3 минуты.Но мы могли бы сделать это также с display:inline-block и float:right или с <table> -имитацией, используя display:table, display:table-row и display:table-cell.По моему мнению, display: flex является относительно новым и не полностью совместимым с различными браузерами (например, IE10, IE11 и другие).Из-за всего этого я хотел бы предложить вам 3 решения.

Решение с display:inline-block и float:right

С display:inline-block размер всегда выравнивается посодержание.

.container,
.item div,
.item b{display:inline-block}
.item b
{
    float:right;
    width:16px;
    height:16px;
    margin-left:5px;
    cursor:pointer;
    background:url("https://i.stack.imgur.com/bKWrw.png")
}
<div class="container">
    <div class="item">
        <div>Synonyms</div>
        <b></b>
    </div>
    <div class="item">
        <div>Concept</div>
        <b></b>
    </div>
    <div class="item">
        <div>Term</div>
        <b></b>
    </div>
</div>
<br style="clear:both">

Решение с <table> -имитацией

.container{display:table}
.item{display:table-row}
.item div{display:table-cell; vertical-align:top}
.item b
{
    display:inline-block;
    width:16px;
    height:16px;
    cursor:pointer;
    margin-left:5px;
    background:url("https://i.stack.imgur.com/bKWrw.png")
}
<div class="container">
    <div class="item">
        <div>Synonyms</div>
        <div><b></b></div>
    </div>
    <div class="item">
        <div>Concept</div>
        <div><b></b></div>
    </div>
    <div class="item">
        <div>Term</div>
        <div><b></b></div>
    </div>
</div>

Решение с классическим <table> (мой любимый)

.menu td{vertical-align:top}
.menu td b
{
    display:inline-block;
    width:16px;
    height:16px;
    cursor:pointer;
    margin-left:5px;
    background:url("https://i.stack.imgur.com/bKWrw.png")
}
<table class="menu" cellpadding="0" cellspacing="0">
<tr>
    <td>Synonyms</td>
    <td><b></b></td>
</tr>
<tr>
    <td>Concept</td>
    <td><b></b></td>
</tr>
<tr>
    <td>Term</td>
    <td><b></b></td>
</tr>
</table>
0 голосов
/ 09 декабря 2018

Я думаю, что, строго говоря, со всеми изложенными вами ограничениями ответ «нет, это невозможно».

Но, поскольку я считаю, что вам нужно, во всяком случае, группироватькаким-то образом эти расширители (альтернативой является то, что каждый расширитель на странице будет выровнен по горизонтали, к которому также не существует полного решения CSS), вы можете просто рассчитывать на блочную модель CSS.

<div style="background-color: #efffef; display: inline-block;">
   <div style="position: relative; padding-right: 36px;">
      <span>Title 1</span>
      <span style="position: absolute; right: 0; top: 0;">[x]</span>
   </div>
   <div style="position: relative; padding-right: 36px;">
      <span>Long title 2</span>
      <span style="position: absolute; right: 0; top: 0;">[x]</span>
   </div>
   <div style="position: relative; padding-right: 36px;">
      <span>Title 3, at your service</span>
      <span style="position: absolute; right: 0; top: 0;">[x]</span>
   </div>
</div>
0 голосов
/ 09 декабря 2018

Вы также можете попробовать использовать свойство css display "table-row" и "table-cell".

style:

   .trow {
        display: table-row;
      }
      .tcell
     {
        display: table-cell;
        padding: 2px;
      }

Вы можете создать свой компонент следующим образом.

const Expander = ({children, title, onExpandToggle, isExpanded}) => (
    <div className="trow">
     <div className="tcell">{title}</div>
      <div className="tcell"><img src={...} onClick={onExpandToggle} /></div>
      {isExpanded && children}
    </div>
  );

Пример

      .trow {
        display: table-row;
      }
      .tcell
     {
        display: table-cell;
        padding: 2px;
      }
    <div>
      <div class="trow">
        <div class="tcell">Synonyms</div>
        <div class="tcell">X</div>
      </div>
      <div class="trow">
        <div class="tcell">Concept</div>
        <div class="tcell">X</div>
      </div>
      <div class="trow">
        <div class="tcell">Term</div>
        <div class="tcell">X</div>
      </div>
    </div>
0 голосов
/ 09 декабря 2018

Предполагая, что у вас будет контейнер для вашего компонента, вы можете установить display: flex для внутреннего контейнера и align-self: flex-end для вашего изображения.

Затем оберните ваш компонент с элементом div, который имеет display: inline-block, который принимает ширину самого большого элемента внутри.

Вот пример:

.container{
  display: inline-block;
  padding: 3px;
}

.item{
  display: flex;
  flex-direction: row; 
  justify-content: space-between;
}

.item .plus{
  width: 15px;
  height: 15px;
  background-image: url("https://cdn1.iconfinder.com/data/icons/mix-color-3/502/Untitled-43-512.png");
  background-size: cover;
  background-repeat: no-repeat;
  align-self: flex-end;
  margin-left: 10px;
}
<div class="container">
  <div class="item">
    <div>Synonyms</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div>Concept</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div>Term</div>
    <div class="plus"></div>
  </div>
</div>

Влияние на все экземпляры без общего контейнера или фиксированной ширины и только с помощью CSS невозможно, поскольку нет доступа к родительскому элементу с помощью CSS,Поэтому, если у вас будет внутренний экземпляр (самый большой), он не сможет применить свою ширину к своему собственному родителю или любому предку и другим дочерним элементам.

Если вы ищете решение, в котором все экземпляры на странице будут иметь одинаковый размер без совместного использования контейнера, вы можете достичь этого с помощью JS.

Рассчитать ширину каждого экземпляра, сохраните самое большое, затем установите эту ширину для остальных экземпляров.В этом примере я также выделяю самый большой элемент.Элементы расположены по всей странице и могут находиться внутри различных элементов div и дисплеев или без контейнера.

var biggestWidth = 0;
var biggestItem;

function setWidth() {
  $(".item").each(function() {
    var currentWidth = $(this).width();
    if (currentWidth > biggestWidth) {
      biggestWidth = Math.ceil(currentWidth);
      biggestItem = $(this);
    }
  });

  $(".item").width(biggestWidth);
  biggestItem.addClass("biggest");
}

$(setWidth());
section {
  width: 40%;
  float: left;
  border: 1px solid black;
  border-radius: 3px;
  margin: 10px;
  padding: 10px;
}

.s1 {
  background-color: #e5e5e5;
  display: table;
}

.item {
  display: inline-block;
  clear: both;
  float: left;
}

.txt {
  float: left;
  display: inline-block;
}

.plus {
  width: 15px;
  height: 15px;
  background-image: url("https://cdn1.iconfinder.com/data/icons/mix-color-3/502/Untitled-43-512.png");
  background-size: cover;
  margin-left: 10px;
  float: right;
}

.shift{
  margin-left: 30%;
}

.clear{
  clear: both;
}

.biggest{
  background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="s1">
  <div class="item">
    <div class="txt">Synonyms</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Concept</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Term</div>
    <div class="plus"></div>
  </div>
</section>
<section>
  <div class="item">
    <div class="txt">Synonyms</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Concept</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Term</div>
    <div class="plus"></div>
  </div>
</section>

<section>
  <div class="item">
    <div class="txt">Synonyms - Long</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Concept</div>
    <div class="plus"></div>
  </div>
  <div class="item">
    <div class="txt">Term</div>
    <div class="plus"></div>
  </div>
</section>

<div class="item">
  <div class="txt">Synonyms</div>
  <div class="plus"></div>
</div>
<div class="item">
  <div class="txt">Concept</div>
  <div class="plus"></div>
</div>
<div class="item">
  <div class="txt">Term</div>
  <div class="plus"></div>
</div>

<div class="clear"></div>
<div class="shift">
<div class="item">
  <div class="txt">Synonyms</div>
  <div class="plus"></div>
</div>
<div class="item">
  <div class="txt">Concept</div>
  <div class="plus"></div>
</div>
<div class="item">
  <div class="txt">The bigget item is here</div>
  <div class="plus"></div>
</div>
</div>
...